@akasecurity/ai-tc-claude-code 0.9.7 → 0.9.8
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/package.json +4 -4
- package/scripts/apply-suppressions.js +283 -132
- package/scripts/backfill.js +1711 -250
- package/scripts/filescan.js +1713 -235
- package/scripts/firstrun.js +1724 -206
- package/scripts/intro.js +171 -47
- package/scripts/message-display.js +279 -128
- package/scripts/onboard.js +261 -110
- package/scripts/post-tool-use.js +1842 -272
- package/scripts/pre-tool-use.js +1841 -284
- package/scripts/query.js +1726 -208
- package/scripts/reconcile.js +1698 -237
- package/scripts/remediate.js +1704 -243
- package/scripts/scan-worker.js +118 -0
- package/scripts/session-start.js +1951 -299
- package/scripts/start-light.js +169 -45
- package/scripts/statusline.js +1726 -206
- package/scripts/stop.js +374 -67
- package/scripts/sync.js +19413 -0
- package/scripts/user-prompt-submit.js +1841 -284
package/scripts/backfill.js
CHANGED
|
@@ -50,7 +50,7 @@ var require_ignore = __commonJS({
|
|
|
50
50
|
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
51
51
|
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
52
52
|
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
53
|
-
var
|
|
53
|
+
var SLASH2 = "/";
|
|
54
54
|
var TMP_KEY_IGNORE = "node-ignore";
|
|
55
55
|
if (typeof Symbol !== "undefined") {
|
|
56
56
|
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
@@ -422,11 +422,11 @@ var require_ignore = __commonJS({
|
|
|
422
422
|
if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
|
|
423
423
|
return this.test(path);
|
|
424
424
|
}
|
|
425
|
-
const slices = path.split(
|
|
425
|
+
const slices = path.split(SLASH2).filter(Boolean);
|
|
426
426
|
slices.pop();
|
|
427
427
|
if (slices.length) {
|
|
428
428
|
const parent = this._t(
|
|
429
|
-
slices.join(
|
|
429
|
+
slices.join(SLASH2) + SLASH2,
|
|
430
430
|
this._testCache,
|
|
431
431
|
true,
|
|
432
432
|
slices
|
|
@@ -442,14 +442,14 @@ var require_ignore = __commonJS({
|
|
|
442
442
|
return cache[path];
|
|
443
443
|
}
|
|
444
444
|
if (!slices) {
|
|
445
|
-
slices = path.split(
|
|
445
|
+
slices = path.split(SLASH2).filter(Boolean);
|
|
446
446
|
}
|
|
447
447
|
slices.pop();
|
|
448
448
|
if (!slices.length) {
|
|
449
449
|
return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE);
|
|
450
450
|
}
|
|
451
451
|
const parent = this._t(
|
|
452
|
-
slices.join(
|
|
452
|
+
slices.join(SLASH2) + SLASH2,
|
|
453
453
|
cache,
|
|
454
454
|
checkUnignored,
|
|
455
455
|
slices
|
|
@@ -492,16 +492,15 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// src/backfill.ts
|
|
495
|
-
import { fileURLToPath as
|
|
495
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
496
496
|
|
|
497
497
|
// ../../packages/plugin-sdk/src/config.ts
|
|
498
|
-
import { existsSync as
|
|
499
|
-
import { join as
|
|
498
|
+
import { existsSync as existsSync6 } from "fs";
|
|
499
|
+
import { join as join10 } from "path";
|
|
500
500
|
|
|
501
|
-
// ../../packages/persistence/src/
|
|
502
|
-
import {
|
|
503
|
-
import { join
|
|
504
|
-
import { DatabaseSync } from "node:sqlite";
|
|
501
|
+
// ../../packages/persistence/src/control-plane-credential.ts
|
|
502
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
|
|
503
|
+
import { join } from "path";
|
|
505
504
|
|
|
506
505
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
507
506
|
var SQLITE_MIGRATIONS = [
|
|
@@ -16208,6 +16207,125 @@ var ConfigScanRecord = external_exports.object({
|
|
|
16208
16207
|
findings: external_exports.array(ConfigPostureFindingInput).optional()
|
|
16209
16208
|
});
|
|
16210
16209
|
|
|
16210
|
+
// ../../packages/schema/src/zod/control-plane.ts
|
|
16211
|
+
var ATTACHED_CREDENTIAL_FILENAME = "control-plane-credential.json";
|
|
16212
|
+
var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
|
|
16213
|
+
var AttachedCredential = external_exports.object({
|
|
16214
|
+
specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
|
|
16215
|
+
// The control-plane endpoint this credential was minted against.
|
|
16216
|
+
endpoint: external_exports.string().min(1),
|
|
16217
|
+
// The bearer credential itself. Never logged, never rendered — status
|
|
16218
|
+
// surfaces show `keyPrefix` and nothing else.
|
|
16219
|
+
apiKey: external_exports.string().min(1),
|
|
16220
|
+
// First few characters of the key, safe to display so a user can match the
|
|
16221
|
+
// credential against their organization's key list.
|
|
16222
|
+
keyPrefix: external_exports.string().min(1).max(16).optional(),
|
|
16223
|
+
mintedAt: external_exports.iso.datetime().optional()
|
|
16224
|
+
});
|
|
16225
|
+
var MAX_DATE_MS = 253402300799999;
|
|
16226
|
+
var MAX_INT4 = 2147483647;
|
|
16227
|
+
var StorePosturePack = external_exports.object({
|
|
16228
|
+
packId: external_exports.string().min(1),
|
|
16229
|
+
// 'namespace/packId'
|
|
16230
|
+
version: external_exports.string().min(1),
|
|
16231
|
+
enabled: external_exports.boolean(),
|
|
16232
|
+
// Stringified pass-through of the local store's `installed_packs.updated_at`
|
|
16233
|
+
// — the column format is store-version-dependent (epoch millis vs ISO), so
|
|
16234
|
+
// the wire shape assumes neither.
|
|
16235
|
+
updatedAt: external_exports.string().nullable()
|
|
16236
|
+
}).meta({ id: "StorePosturePack" });
|
|
16237
|
+
var StorePosturePolicyCounts = external_exports.object({
|
|
16238
|
+
total: external_exports.number().int().min(0),
|
|
16239
|
+
disabled: external_exports.number().int().min(0),
|
|
16240
|
+
// Exhaustive per-action map; the builder pre-fills every action with 0.
|
|
16241
|
+
//
|
|
16242
|
+
// Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
|
|
16243
|
+
// enforces exhaustiveness either way, but z.record emits `propertyNames` +
|
|
16244
|
+
// `additionalProperties` into a generated schema document, and a type
|
|
16245
|
+
// generator renders THAT with every key optional — a sender built against
|
|
16246
|
+
// the generated type would typecheck and still be rejected at runtime. An
|
|
16247
|
+
// explicit object emits `properties` + `required`, so generated types
|
|
16248
|
+
// demand all five.
|
|
16249
|
+
//
|
|
16250
|
+
// `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
|
|
16251
|
+
// ActionTaken member is a COMPILE error here instead of silent drift.
|
|
16252
|
+
// `.strict()` is load-bearing — it rejects an unknown action key, which a
|
|
16253
|
+
// bare object would silently STRIP, accepting a miscounted map as valid.
|
|
16254
|
+
byAction: external_exports.object({
|
|
16255
|
+
warn: external_exports.number().int().min(0),
|
|
16256
|
+
redact: external_exports.number().int().min(0),
|
|
16257
|
+
block: external_exports.number().int().min(0),
|
|
16258
|
+
allow: external_exports.number().int().min(0),
|
|
16259
|
+
log: external_exports.number().int().min(0)
|
|
16260
|
+
}).strict()
|
|
16261
|
+
}).meta({ id: "StorePosturePolicyCounts" });
|
|
16262
|
+
var StorePosturePlugin = external_exports.object({
|
|
16263
|
+
/** Package name of the reporting plugin. */
|
|
16264
|
+
package: external_exports.string().min(1).max(200),
|
|
16265
|
+
version: external_exports.string().min(1).max(64),
|
|
16266
|
+
/** Version of the bundled core, when the build records one separately. */
|
|
16267
|
+
ossVersion: external_exports.string().max(64).nullable(),
|
|
16268
|
+
/**
|
|
16269
|
+
* `version` of the policy bundle this machine last fetched. Bounded at 200
|
|
16270
|
+
* rather than the 64 a bare sha256 hex digest needs today, so a later
|
|
16271
|
+
* format with an algorithm prefix does not start rejecting the channel.
|
|
16272
|
+
*/
|
|
16273
|
+
policyBundleVersion: external_exports.string().max(200).nullable(),
|
|
16274
|
+
/** Epoch millis, on the CLIENT clock, of that fetch. */
|
|
16275
|
+
policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
|
|
16276
|
+
}).meta({ id: "StorePosturePlugin" });
|
|
16277
|
+
var StorePostureSnapshot = external_exports.object({
|
|
16278
|
+
deviceId: external_exports.guid(),
|
|
16279
|
+
hostname: external_exports.string().min(1).max(253),
|
|
16280
|
+
// Epoch millis on the CLIENT clock. Bounded by what a receiving store
|
|
16281
|
+
// accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
|
|
16282
|
+
capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
|
|
16283
|
+
// False is a measurement, not an error state: "no local store exists on
|
|
16284
|
+
// this machine".
|
|
16285
|
+
storePresent: external_exports.boolean(),
|
|
16286
|
+
schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
|
|
16287
|
+
// PRAGMA user_version
|
|
16288
|
+
findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
|
|
16289
|
+
// Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
|
|
16290
|
+
// bound does and does not do. Worth stating for these two specifically:
|
|
16291
|
+
// they are read from the local store's own ROWS rather than from this
|
|
16292
|
+
// machine's clock, so a damaged or hand-edited store is enough to produce
|
|
16293
|
+
// an out-of-range value with no clock skew involved.
|
|
16294
|
+
findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
|
|
16295
|
+
findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
|
|
16296
|
+
packs: external_exports.array(StorePosturePack).max(500),
|
|
16297
|
+
policyCounts: StorePosturePolicyCounts,
|
|
16298
|
+
// OPTIONAL, not nullable: a reporter that predates this member keeps
|
|
16299
|
+
// getting its 200 without a payload change.
|
|
16300
|
+
plugin: StorePosturePlugin.optional()
|
|
16301
|
+
}).meta({ id: "StorePostureSnapshot" });
|
|
16302
|
+
var CAPTURE_VERSION_PREFIX = "capture/";
|
|
16303
|
+
var RecordAuditEventRequest = AuditEventInput.extend({
|
|
16304
|
+
inspections: external_exports.array(ToolCallInspection).default([])
|
|
16305
|
+
}).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
|
|
16306
|
+
message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
|
|
16307
|
+
path: ["inspections"]
|
|
16308
|
+
}).meta({ id: "RecordAuditEventRequest" });
|
|
16309
|
+
var IngestAck = external_exports.object({
|
|
16310
|
+
accepted: external_exports.number().int().nonnegative(),
|
|
16311
|
+
duplicates: external_exports.number().int().nonnegative()
|
|
16312
|
+
});
|
|
16313
|
+
var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
|
|
16314
|
+
var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
|
|
16315
|
+
var PluginWhoami = external_exports.object({
|
|
16316
|
+
tenantName: printable(200),
|
|
16317
|
+
userEmail: printable(320),
|
|
16318
|
+
role: printable(64),
|
|
16319
|
+
keyKind: printable(64),
|
|
16320
|
+
serverTime: printable(64)
|
|
16321
|
+
});
|
|
16322
|
+
var ControlPlaneErrorBody = external_exports.object({
|
|
16323
|
+
error: external_exports.object({
|
|
16324
|
+
code: external_exports.string().optional(),
|
|
16325
|
+
message: external_exports.string().optional()
|
|
16326
|
+
}).optional()
|
|
16327
|
+
});
|
|
16328
|
+
|
|
16211
16329
|
// ../../packages/schema/src/zod/registry.ts
|
|
16212
16330
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
16213
16331
|
var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -16510,15 +16628,15 @@ function summaryToDetectionListItem(s) {
|
|
|
16510
16628
|
}
|
|
16511
16629
|
function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
16512
16630
|
const rules = row.rules.flatMap((r) => {
|
|
16513
|
-
const
|
|
16514
|
-
if (!
|
|
16631
|
+
const parsed2 = Matcher.safeParse(r.matcher);
|
|
16632
|
+
if (!parsed2.success) return [];
|
|
16515
16633
|
return [
|
|
16516
16634
|
{
|
|
16517
16635
|
id: r.id,
|
|
16518
16636
|
name: r.name,
|
|
16519
16637
|
category: r.category,
|
|
16520
16638
|
severity: r.severity,
|
|
16521
|
-
matcher:
|
|
16639
|
+
matcher: parsed2.data
|
|
16522
16640
|
}
|
|
16523
16641
|
];
|
|
16524
16642
|
});
|
|
@@ -17164,8 +17282,8 @@ function toApiAction(dbVal) {
|
|
|
17164
17282
|
}
|
|
17165
17283
|
function toApiCategory(dbVal) {
|
|
17166
17284
|
if (dbVal === "code_context") return "source_code";
|
|
17167
|
-
const
|
|
17168
|
-
return
|
|
17285
|
+
const parsed2 = FindingCategory.safeParse(dbVal);
|
|
17286
|
+
return parsed2.success ? parsed2.data : "custom";
|
|
17169
17287
|
}
|
|
17170
17288
|
function toApiProvider(sourceTool) {
|
|
17171
17289
|
return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
|
|
@@ -17802,6 +17920,9 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17802
17920
|
function defaultWorkspaceSettings() {
|
|
17803
17921
|
return WorkspaceSettings.parse({});
|
|
17804
17922
|
}
|
|
17923
|
+
function isAttached(settings) {
|
|
17924
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
17925
|
+
}
|
|
17805
17926
|
function toInventoryRow(input, id, now) {
|
|
17806
17927
|
return {
|
|
17807
17928
|
id,
|
|
@@ -18069,8 +18190,8 @@ function builtinPolicyIsReversible(id) {
|
|
|
18069
18190
|
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
18070
18191
|
}
|
|
18071
18192
|
function policyIdIsReversible(policyId) {
|
|
18072
|
-
const
|
|
18073
|
-
const id =
|
|
18193
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18194
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
18074
18195
|
return builtinPolicyIsReversible(id);
|
|
18075
18196
|
}
|
|
18076
18197
|
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
@@ -18081,8 +18202,8 @@ var BUILTIN_POLICIES = Object.fromEntries(
|
|
|
18081
18202
|
);
|
|
18082
18203
|
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
18083
18204
|
function policyIdToAction(policyId) {
|
|
18084
|
-
const
|
|
18085
|
-
const id =
|
|
18205
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18206
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
18086
18207
|
return BUILTIN_POLICIES[id].action;
|
|
18087
18208
|
}
|
|
18088
18209
|
var UsedByItem = external_exports.object({
|
|
@@ -18525,53 +18646,6 @@ function reviewSeverityRank(reasons) {
|
|
|
18525
18646
|
return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
|
|
18526
18647
|
}
|
|
18527
18648
|
|
|
18528
|
-
// ../../packages/persistence/src/ids.ts
|
|
18529
|
-
import { createHash } from "crypto";
|
|
18530
|
-
function sha256Hex(input) {
|
|
18531
|
-
return createHash("sha256").update(input).digest("hex");
|
|
18532
|
-
}
|
|
18533
|
-
function inventoryId(objectType, identityKey) {
|
|
18534
|
-
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
18535
|
-
}
|
|
18536
|
-
function sourceProjectId(url2) {
|
|
18537
|
-
return sha256Hex(canonicalIdentity(["source_project", url2]));
|
|
18538
|
-
}
|
|
18539
|
-
function classifiedDataId(cls) {
|
|
18540
|
-
return sha256Hex(canonicalIdentity(["classified_data", cls]));
|
|
18541
|
-
}
|
|
18542
|
-
function inspectionDefinitionId(ruleId, version2) {
|
|
18543
|
-
return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
|
|
18544
|
-
}
|
|
18545
|
-
function llmCallId(sessionId, messageId) {
|
|
18546
|
-
return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
|
|
18547
|
-
}
|
|
18548
|
-
function toolCallId(sessionId, toolUseId) {
|
|
18549
|
-
return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
|
|
18550
|
-
}
|
|
18551
|
-
function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
|
|
18552
|
-
return sha256Hex(
|
|
18553
|
-
canonicalIdentity([
|
|
18554
|
-
"inspection_finding",
|
|
18555
|
-
auditEventId,
|
|
18556
|
-
ruleId,
|
|
18557
|
-
String(spanStart),
|
|
18558
|
-
String(spanEnd)
|
|
18559
|
-
])
|
|
18560
|
-
);
|
|
18561
|
-
}
|
|
18562
|
-
var NO_SESSION = "no_session";
|
|
18563
|
-
var NO_PATH = "no_path";
|
|
18564
|
-
function captureId(sessionId, contentHash, filePath = null) {
|
|
18565
|
-
return sha256Hex(
|
|
18566
|
-
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
18567
|
-
);
|
|
18568
|
-
}
|
|
18569
|
-
|
|
18570
|
-
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18571
|
-
import { randomUUID } from "crypto";
|
|
18572
|
-
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18573
|
-
import { basename, dirname, join } from "path";
|
|
18574
|
-
|
|
18575
18649
|
// ../../packages/persistence/src/paths.ts
|
|
18576
18650
|
import {
|
|
18577
18651
|
chmodSync,
|
|
@@ -18697,7 +18771,123 @@ function publishByLink(tmp, file2, data) {
|
|
|
18697
18771
|
}
|
|
18698
18772
|
}
|
|
18699
18773
|
|
|
18774
|
+
// ../../packages/persistence/src/control-plane-credential.ts
|
|
18775
|
+
function controlPlaneCredentialPath(settingsDir2) {
|
|
18776
|
+
return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
|
|
18777
|
+
}
|
|
18778
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
18779
|
+
function isSafeEndpoint(endpoint) {
|
|
18780
|
+
let parsed2;
|
|
18781
|
+
try {
|
|
18782
|
+
parsed2 = new URL(endpoint);
|
|
18783
|
+
} catch {
|
|
18784
|
+
return false;
|
|
18785
|
+
}
|
|
18786
|
+
if (parsed2.protocol === "https:") return true;
|
|
18787
|
+
return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
|
|
18788
|
+
}
|
|
18789
|
+
function repairOrRefuseMode(file2) {
|
|
18790
|
+
const link = lstatSync2(file2, { throwIfNoEntry: false });
|
|
18791
|
+
if (link === void 0) return "absent";
|
|
18792
|
+
if (link.isSymbolicLink()) return "untrusted";
|
|
18793
|
+
const stat = statSync(file2, { throwIfNoEntry: false });
|
|
18794
|
+
if (stat === void 0) return "absent";
|
|
18795
|
+
const uid = process.getuid?.();
|
|
18796
|
+
if (uid !== void 0 && stat.uid !== uid) return "untrusted";
|
|
18797
|
+
if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
|
|
18798
|
+
try {
|
|
18799
|
+
chmodSync2(file2, DATA_FILE_MODE);
|
|
18800
|
+
} catch {
|
|
18801
|
+
return "untrusted";
|
|
18802
|
+
}
|
|
18803
|
+
}
|
|
18804
|
+
return "ok";
|
|
18805
|
+
}
|
|
18806
|
+
function readControlPlaneCredentialState(settingsDir2, connection) {
|
|
18807
|
+
const file2 = controlPlaneCredentialPath(settingsDir2);
|
|
18808
|
+
let raw;
|
|
18809
|
+
const gate = repairOrRefuseMode(file2);
|
|
18810
|
+
if (gate === "absent") return { usable: false, reason: "absent" };
|
|
18811
|
+
if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
|
|
18812
|
+
try {
|
|
18813
|
+
raw = readFileSync(file2, "utf8");
|
|
18814
|
+
} catch (err) {
|
|
18815
|
+
const code = err.code;
|
|
18816
|
+
return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
|
|
18817
|
+
}
|
|
18818
|
+
let parsed2;
|
|
18819
|
+
try {
|
|
18820
|
+
parsed2 = JSON.parse(raw);
|
|
18821
|
+
} catch {
|
|
18822
|
+
return { usable: false, reason: "malformed" };
|
|
18823
|
+
}
|
|
18824
|
+
const result = AttachedCredential.safeParse(parsed2);
|
|
18825
|
+
if (!result.success) return { usable: false, reason: "malformed" };
|
|
18826
|
+
if (!isSafeEndpoint(result.data.endpoint)) {
|
|
18827
|
+
return { usable: false, reason: "unsafe-endpoint" };
|
|
18828
|
+
}
|
|
18829
|
+
if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
|
|
18830
|
+
return {
|
|
18831
|
+
usable: false,
|
|
18832
|
+
reason: "endpoint-mismatch",
|
|
18833
|
+
credentialEndpoint: result.data.endpoint,
|
|
18834
|
+
settingsEndpoint: connection.endpoint
|
|
18835
|
+
};
|
|
18836
|
+
}
|
|
18837
|
+
return { usable: true, credential: result.data };
|
|
18838
|
+
}
|
|
18839
|
+
|
|
18840
|
+
// ../../packages/persistence/src/database.ts
|
|
18841
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
18842
|
+
import { join as join3, sep } from "path";
|
|
18843
|
+
import { DatabaseSync } from "node:sqlite";
|
|
18844
|
+
|
|
18845
|
+
// ../../packages/persistence/src/ids.ts
|
|
18846
|
+
import { createHash } from "crypto";
|
|
18847
|
+
function sha256Hex(input) {
|
|
18848
|
+
return createHash("sha256").update(input).digest("hex");
|
|
18849
|
+
}
|
|
18850
|
+
function inventoryId(objectType, identityKey) {
|
|
18851
|
+
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
18852
|
+
}
|
|
18853
|
+
function sourceProjectId(url2) {
|
|
18854
|
+
return sha256Hex(canonicalIdentity(["source_project", url2]));
|
|
18855
|
+
}
|
|
18856
|
+
function classifiedDataId(cls) {
|
|
18857
|
+
return sha256Hex(canonicalIdentity(["classified_data", cls]));
|
|
18858
|
+
}
|
|
18859
|
+
function inspectionDefinitionId(ruleId, version2) {
|
|
18860
|
+
return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
|
|
18861
|
+
}
|
|
18862
|
+
function llmCallId(sessionId, messageId) {
|
|
18863
|
+
return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
|
|
18864
|
+
}
|
|
18865
|
+
function toolCallId(sessionId, toolUseId) {
|
|
18866
|
+
return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
|
|
18867
|
+
}
|
|
18868
|
+
function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
|
|
18869
|
+
return sha256Hex(
|
|
18870
|
+
canonicalIdentity([
|
|
18871
|
+
"inspection_finding",
|
|
18872
|
+
auditEventId,
|
|
18873
|
+
ruleId,
|
|
18874
|
+
String(spanStart),
|
|
18875
|
+
String(spanEnd)
|
|
18876
|
+
])
|
|
18877
|
+
);
|
|
18878
|
+
}
|
|
18879
|
+
var NO_SESSION = "no_session";
|
|
18880
|
+
var NO_PATH = "no_path";
|
|
18881
|
+
function captureId(sessionId, contentHash, filePath = null) {
|
|
18882
|
+
return sha256Hex(
|
|
18883
|
+
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
18884
|
+
);
|
|
18885
|
+
}
|
|
18886
|
+
|
|
18700
18887
|
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18888
|
+
import { randomUUID } from "crypto";
|
|
18889
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
|
|
18890
|
+
import { basename, dirname, join as join2 } from "path";
|
|
18701
18891
|
function backupPath(file2, tag) {
|
|
18702
18892
|
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18703
18893
|
}
|
|
@@ -18707,15 +18897,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
|
|
|
18707
18897
|
var SNAPSHOT_STAGING_COPY = "copy";
|
|
18708
18898
|
function createSnapshotStaging(backup) {
|
|
18709
18899
|
const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
|
|
18710
|
-
|
|
18900
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18711
18901
|
mkdirOwnerOnlySync(stage);
|
|
18712
18902
|
tightenDir(stage);
|
|
18713
|
-
return { stage, copy:
|
|
18903
|
+
return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
|
|
18714
18904
|
}
|
|
18715
18905
|
function idleMs(entry) {
|
|
18716
|
-
for (const candidate of [
|
|
18906
|
+
for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
|
|
18717
18907
|
try {
|
|
18718
|
-
return Date.now() -
|
|
18908
|
+
return Date.now() - statSync2(candidate).mtimeMs;
|
|
18719
18909
|
} catch {
|
|
18720
18910
|
}
|
|
18721
18911
|
}
|
|
@@ -18732,11 +18922,11 @@ function reapStalePartials(file2) {
|
|
|
18732
18922
|
}
|
|
18733
18923
|
for (const name of entries) {
|
|
18734
18924
|
if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
|
|
18735
|
-
const staging =
|
|
18925
|
+
const staging = join2(dir, name);
|
|
18736
18926
|
try {
|
|
18737
18927
|
const idle = idleMs(staging);
|
|
18738
18928
|
if (idle !== null && idle > STALE_PARTIAL_MS) {
|
|
18739
|
-
|
|
18929
|
+
rmSync3(staging, { recursive: true, force: true });
|
|
18740
18930
|
}
|
|
18741
18931
|
} catch {
|
|
18742
18932
|
}
|
|
@@ -18750,13 +18940,13 @@ function snapshotStore(db, backup) {
|
|
|
18750
18940
|
renameSync2(copy, backup);
|
|
18751
18941
|
} catch (error51) {
|
|
18752
18942
|
try {
|
|
18753
|
-
|
|
18943
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18754
18944
|
} catch {
|
|
18755
18945
|
}
|
|
18756
18946
|
throw error51;
|
|
18757
18947
|
}
|
|
18758
18948
|
try {
|
|
18759
|
-
|
|
18949
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18760
18950
|
} catch {
|
|
18761
18951
|
}
|
|
18762
18952
|
}
|
|
@@ -18771,7 +18961,7 @@ function moveStoreAside(file2, backup) {
|
|
|
18771
18961
|
renameSync2(sidecar, moved);
|
|
18772
18962
|
undo.push([moved, sidecar]);
|
|
18773
18963
|
} catch {
|
|
18774
|
-
|
|
18964
|
+
rmSync3(sidecar, { force: true });
|
|
18775
18965
|
}
|
|
18776
18966
|
}
|
|
18777
18967
|
} catch (error51) {
|
|
@@ -18787,14 +18977,14 @@ function moveStoreAside(file2, backup) {
|
|
|
18787
18977
|
}
|
|
18788
18978
|
function discardStore(file2, backup) {
|
|
18789
18979
|
try {
|
|
18790
|
-
|
|
18980
|
+
rmSync3(file2, { force: true });
|
|
18791
18981
|
for (const sidecar of dbSidecars(file2)) {
|
|
18792
|
-
|
|
18982
|
+
rmSync3(sidecar, { force: true });
|
|
18793
18983
|
}
|
|
18794
18984
|
} catch (error51) {
|
|
18795
18985
|
if (existsSync(file2)) {
|
|
18796
18986
|
try {
|
|
18797
|
-
|
|
18987
|
+
rmSync3(backup, { force: true });
|
|
18798
18988
|
} catch {
|
|
18799
18989
|
}
|
|
18800
18990
|
}
|
|
@@ -19026,10 +19216,31 @@ function applyMigrations(db, file2) {
|
|
|
19026
19216
|
if (drained) applyLegacyDropMigration(db, file2);
|
|
19027
19217
|
}
|
|
19028
19218
|
}
|
|
19219
|
+
function readLegacyTables(db) {
|
|
19220
|
+
let holdsRows = false;
|
|
19221
|
+
const marks = [];
|
|
19222
|
+
for (const table of ["events", "findings"]) {
|
|
19223
|
+
try {
|
|
19224
|
+
const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table}`).get();
|
|
19225
|
+
if (row === void 0) {
|
|
19226
|
+
holdsRows = true;
|
|
19227
|
+
marks.push(`${table}:unreadable`);
|
|
19228
|
+
continue;
|
|
19229
|
+
}
|
|
19230
|
+
if (row.n > 0) holdsRows = true;
|
|
19231
|
+
marks.push(`${table}:${String(row.n)}:${String(row.hi)}`);
|
|
19232
|
+
} catch {
|
|
19233
|
+
holdsRows = true;
|
|
19234
|
+
marks.push(`${table}:unreadable`);
|
|
19235
|
+
}
|
|
19236
|
+
}
|
|
19237
|
+
return { holdsRows, mark: marks.join("|") };
|
|
19238
|
+
}
|
|
19029
19239
|
function applyLegacyDropMigration(db, file2) {
|
|
19030
19240
|
const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
|
|
19031
19241
|
if (!migration) return;
|
|
19032
|
-
|
|
19242
|
+
const before = file2 === void 0 ? void 0 : readLegacyTables(db);
|
|
19243
|
+
if (file2 !== void 0 && before?.holdsRows === true) {
|
|
19033
19244
|
try {
|
|
19034
19245
|
backupBeforeLegacyDrop(db, file2);
|
|
19035
19246
|
} catch (error51) {
|
|
@@ -19043,6 +19254,12 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
19043
19254
|
() => {
|
|
19044
19255
|
const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
|
|
19045
19256
|
if (alreadyDropped) return;
|
|
19257
|
+
if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
|
|
19258
|
+
akaWarn(
|
|
19259
|
+
"legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
|
|
19260
|
+
);
|
|
19261
|
+
return;
|
|
19262
|
+
}
|
|
19046
19263
|
for (const statement of splitStatements(migration.sql)) {
|
|
19047
19264
|
db.exec(statement);
|
|
19048
19265
|
}
|
|
@@ -19397,8 +19614,8 @@ function safeJson(s, fallback) {
|
|
|
19397
19614
|
function parseJsonObject(s) {
|
|
19398
19615
|
if (s == null) return void 0;
|
|
19399
19616
|
try {
|
|
19400
|
-
const
|
|
19401
|
-
if (typeof
|
|
19617
|
+
const parsed2 = JSON.parse(s);
|
|
19618
|
+
if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
|
|
19402
19619
|
} catch {
|
|
19403
19620
|
}
|
|
19404
19621
|
return void 0;
|
|
@@ -19409,16 +19626,16 @@ function encodeKeysetCursor(payload) {
|
|
|
19409
19626
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19410
19627
|
}
|
|
19411
19628
|
function decodeKeysetCursor(cursor) {
|
|
19412
|
-
const
|
|
19413
|
-
if (
|
|
19629
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19630
|
+
if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19414
19631
|
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19415
19632
|
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19416
19633
|
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19417
19634
|
// outcome a cursor that does not decode must never produce, since the
|
|
19418
19635
|
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19419
19636
|
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19420
|
-
Number.isInteger(
|
|
19421
|
-
return
|
|
19637
|
+
Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
|
|
19638
|
+
return parsed2;
|
|
19422
19639
|
}
|
|
19423
19640
|
return null;
|
|
19424
19641
|
}
|
|
@@ -19483,18 +19700,18 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
19483
19700
|
};
|
|
19484
19701
|
function safeParseStringArray(raw) {
|
|
19485
19702
|
if (!raw) return [];
|
|
19486
|
-
const
|
|
19487
|
-
return Array.isArray(
|
|
19703
|
+
const parsed2 = safeJson(raw, null);
|
|
19704
|
+
return Array.isArray(parsed2) ? parsed2 : [];
|
|
19488
19705
|
}
|
|
19489
19706
|
var DEFAULT_HARNESS = HARNESS.ClaudeCode;
|
|
19490
19707
|
function toHarness(raw) {
|
|
19491
|
-
const
|
|
19492
|
-
return
|
|
19708
|
+
const parsed2 = Harness.safeParse(raw);
|
|
19709
|
+
return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
|
|
19493
19710
|
}
|
|
19494
19711
|
function resolveLifecycle(row, lastActivityMs, nowMs) {
|
|
19495
19712
|
if (row.status) {
|
|
19496
|
-
const
|
|
19497
|
-
if (
|
|
19713
|
+
const parsed2 = SessionStatus.safeParse(row.status);
|
|
19714
|
+
if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
|
|
19498
19715
|
}
|
|
19499
19716
|
if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
|
|
19500
19717
|
if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
|
|
@@ -20453,9 +20670,9 @@ var SqliteDetectionsRepository = class {
|
|
|
20453
20670
|
const ruleIds = /* @__PURE__ */ new Set();
|
|
20454
20671
|
for (const r of rows) {
|
|
20455
20672
|
if (intToBool(r.enabled)) active += 1;
|
|
20456
|
-
const
|
|
20457
|
-
rules +=
|
|
20458
|
-
for (const rule of
|
|
20673
|
+
const parsed2 = parseRules(r.rulesJson);
|
|
20674
|
+
rules += parsed2.length;
|
|
20675
|
+
for (const rule of parsed2) {
|
|
20459
20676
|
if (typeof rule.id === "string") ruleIds.add(rule.id);
|
|
20460
20677
|
}
|
|
20461
20678
|
}
|
|
@@ -20989,12 +21206,12 @@ function encodeGroupCursor(group) {
|
|
|
20989
21206
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20990
21207
|
}
|
|
20991
21208
|
function decodeGroupCursor(cursor) {
|
|
20992
|
-
const
|
|
20993
|
-
if (
|
|
21209
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
21210
|
+
if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
|
|
20994
21211
|
return {
|
|
20995
|
-
severity:
|
|
20996
|
-
latestDetectedAt:
|
|
20997
|
-
id:
|
|
21212
|
+
severity: parsed2.sev,
|
|
21213
|
+
latestDetectedAt: parsed2.t,
|
|
21214
|
+
id: parsed2.id
|
|
20998
21215
|
};
|
|
20999
21216
|
}
|
|
21000
21217
|
return null;
|
|
@@ -22128,16 +22345,16 @@ var SqliteInstalledPacksRepository = class {
|
|
|
22128
22345
|
continue;
|
|
22129
22346
|
}
|
|
22130
22347
|
for (const entry of raw) {
|
|
22131
|
-
const
|
|
22132
|
-
if (
|
|
22133
|
-
out.rules.push(
|
|
22134
|
-
out.ruleActions.set(
|
|
22135
|
-
out.ruleVersions.set(
|
|
22136
|
-
if (reversible) out.reversibleRules.add(
|
|
22137
|
-
else out.reversibleRules.delete(
|
|
22348
|
+
const parsed2 = Rule.safeParse(entry);
|
|
22349
|
+
if (parsed2.success) {
|
|
22350
|
+
out.rules.push(parsed2.data);
|
|
22351
|
+
out.ruleActions.set(parsed2.data.id, action);
|
|
22352
|
+
out.ruleVersions.set(parsed2.data.id, row.version);
|
|
22353
|
+
if (reversible) out.reversibleRules.add(parsed2.data.id);
|
|
22354
|
+
else out.reversibleRules.delete(parsed2.data.id);
|
|
22138
22355
|
} else {
|
|
22139
22356
|
out.invalidRules += 1;
|
|
22140
|
-
reject(pack, printableRuleId(entry), firstIssueReason(
|
|
22357
|
+
reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
|
|
22141
22358
|
}
|
|
22142
22359
|
}
|
|
22143
22360
|
}
|
|
@@ -23527,15 +23744,15 @@ function encodeReuseCursor(payload) {
|
|
|
23527
23744
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23528
23745
|
}
|
|
23529
23746
|
function decodeReuseCursor(cursor) {
|
|
23530
|
-
const
|
|
23531
|
-
if (
|
|
23747
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23748
|
+
if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23532
23749
|
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23533
23750
|
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23534
23751
|
// malformed cursor must never produce, since restarting from the top is the
|
|
23535
23752
|
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23536
23753
|
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23537
|
-
Number.isInteger(
|
|
23538
|
-
return { occurrences:
|
|
23754
|
+
Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
|
|
23755
|
+
return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
|
|
23539
23756
|
}
|
|
23540
23757
|
return null;
|
|
23541
23758
|
}
|
|
@@ -25264,7 +25481,7 @@ function openAndInitialize(file2) {
|
|
|
25264
25481
|
}
|
|
25265
25482
|
function openLocalDatabase(dir) {
|
|
25266
25483
|
ensureDataDirSync(dir);
|
|
25267
|
-
const file2 =
|
|
25484
|
+
const file2 = join3(dir, DB_FILENAME);
|
|
25268
25485
|
reapStalePartials(file2);
|
|
25269
25486
|
const {
|
|
25270
25487
|
db,
|
|
@@ -25520,9 +25737,9 @@ import {
|
|
|
25520
25737
|
closeSync,
|
|
25521
25738
|
existsSync as existsSync2,
|
|
25522
25739
|
openSync,
|
|
25523
|
-
readFileSync,
|
|
25524
|
-
rmSync as
|
|
25525
|
-
statSync as
|
|
25740
|
+
readFileSync as readFileSync2,
|
|
25741
|
+
rmSync as rmSync4,
|
|
25742
|
+
statSync as statSync3,
|
|
25526
25743
|
writeFileSync as writeFileSync2
|
|
25527
25744
|
} from "fs";
|
|
25528
25745
|
import { hostname as hostname3 } from "os";
|
|
@@ -25540,20 +25757,20 @@ function computeFindingKey(input) {
|
|
|
25540
25757
|
|
|
25541
25758
|
// ../../packages/persistence/src/fingerprint.ts
|
|
25542
25759
|
import { createHmac, randomBytes } from "crypto";
|
|
25543
|
-
import { existsSync as existsSync3, readFileSync as
|
|
25544
|
-
import { join as
|
|
25760
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
25761
|
+
import { join as join4 } from "path";
|
|
25545
25762
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
25546
25763
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
25547
25764
|
var KEY_MATERIAL_BYTES = 32;
|
|
25548
25765
|
function keyFilePath(dataDir2) {
|
|
25549
|
-
return
|
|
25766
|
+
return join4(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
25550
25767
|
}
|
|
25551
25768
|
function parseKeyFile(raw) {
|
|
25552
|
-
const
|
|
25553
|
-
if (typeof
|
|
25769
|
+
const parsed2 = JSON.parse(raw);
|
|
25770
|
+
if (typeof parsed2 !== "object" || parsed2 === null) {
|
|
25554
25771
|
throw new Error("exception key file is corrupt: not a JSON object");
|
|
25555
25772
|
}
|
|
25556
|
-
const { version: version2, material } =
|
|
25773
|
+
const { version: version2, material } = parsed2;
|
|
25557
25774
|
if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
|
|
25558
25775
|
throw new Error("exception key file is corrupt: bad version");
|
|
25559
25776
|
}
|
|
@@ -25584,7 +25801,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
25584
25801
|
}
|
|
25585
25802
|
};
|
|
25586
25803
|
function storedKeyVersionFloor(dataDir2) {
|
|
25587
|
-
const file2 =
|
|
25804
|
+
const file2 = join4(dataDir2, DB_FILENAME);
|
|
25588
25805
|
if (!existsSync3(file2)) return 0;
|
|
25589
25806
|
let db;
|
|
25590
25807
|
try {
|
|
@@ -25639,7 +25856,7 @@ function occupantMessage(file2, kind) {
|
|
|
25639
25856
|
function readFingerprintKey(dataDir2) {
|
|
25640
25857
|
let raw;
|
|
25641
25858
|
try {
|
|
25642
|
-
raw =
|
|
25859
|
+
raw = readFileSync3(keyFilePath(dataDir2), "utf8");
|
|
25643
25860
|
} catch (err) {
|
|
25644
25861
|
if (err.code === "ENOENT") return null;
|
|
25645
25862
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -25665,21 +25882,25 @@ function fingerprintValue(key, raw) {
|
|
|
25665
25882
|
import { renameSync as renameSync3 } from "fs";
|
|
25666
25883
|
import { mkdir } from "fs/promises";
|
|
25667
25884
|
import { homedir } from "os";
|
|
25668
|
-
import { join as
|
|
25885
|
+
import { join as join5 } from "path";
|
|
25669
25886
|
function defaultDataDir() {
|
|
25670
|
-
return
|
|
25887
|
+
return join5(homedir(), ".aka");
|
|
25671
25888
|
}
|
|
25672
25889
|
function settingsDir(base = defaultDataDir()) {
|
|
25673
|
-
return
|
|
25890
|
+
return join5(base, "settings");
|
|
25674
25891
|
}
|
|
25675
25892
|
function dataDir(base = defaultDataDir()) {
|
|
25676
|
-
return
|
|
25893
|
+
return join5(base, "data");
|
|
25677
25894
|
}
|
|
25678
25895
|
function dbPath(base = defaultDataDir()) {
|
|
25679
|
-
return
|
|
25896
|
+
return join5(dataDir(base), "aka.db");
|
|
25680
25897
|
}
|
|
25681
25898
|
function keysDir(base = defaultDataDir()) {
|
|
25682
|
-
return
|
|
25899
|
+
return join5(base, "keys");
|
|
25900
|
+
}
|
|
25901
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
25902
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
25903
|
+
tightenDir(dir);
|
|
25683
25904
|
}
|
|
25684
25905
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
25685
25906
|
ensureDataDirSync(dir);
|
|
@@ -25692,8 +25913,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25692
25913
|
for (const { name, dest } of moves) {
|
|
25693
25914
|
try {
|
|
25694
25915
|
ensureDataDirSync(dest);
|
|
25695
|
-
const moved =
|
|
25696
|
-
renameSync3(
|
|
25916
|
+
const moved = join5(dest, name);
|
|
25917
|
+
renameSync3(join5(base, name), moved);
|
|
25697
25918
|
tightenFile(moved);
|
|
25698
25919
|
} catch {
|
|
25699
25920
|
}
|
|
@@ -25701,7 +25922,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25701
25922
|
}
|
|
25702
25923
|
|
|
25703
25924
|
// ../../packages/persistence/src/managed-settings.ts
|
|
25704
|
-
import { readFileSync as
|
|
25925
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
25705
25926
|
import { posix, win32 } from "path";
|
|
25706
25927
|
function managedSettingsPaths(platform2 = process.platform) {
|
|
25707
25928
|
if (platform2 === "darwin") {
|
|
@@ -25719,14 +25940,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
|
|
|
25719
25940
|
for (const path of paths) {
|
|
25720
25941
|
let text;
|
|
25721
25942
|
try {
|
|
25722
|
-
text =
|
|
25943
|
+
text = readFileSync4(path, "utf8");
|
|
25723
25944
|
} catch {
|
|
25724
25945
|
continue;
|
|
25725
25946
|
}
|
|
25726
25947
|
const record2 = parseJsonObject(text);
|
|
25727
25948
|
if (!record2) continue;
|
|
25728
|
-
const
|
|
25729
|
-
if (
|
|
25949
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
25950
|
+
if (parsed2.success) return parsed2.data;
|
|
25730
25951
|
}
|
|
25731
25952
|
return null;
|
|
25732
25953
|
}
|
|
@@ -25766,14 +25987,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
|
|
|
25766
25987
|
}
|
|
25767
25988
|
|
|
25768
25989
|
// ../../packages/persistence/src/settings.ts
|
|
25769
|
-
import { readFileSync as
|
|
25770
|
-
import { join as
|
|
25990
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
25991
|
+
import { join as join6 } from "path";
|
|
25771
25992
|
var SETTINGS_FILENAME = "settings.json";
|
|
25772
25993
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
25773
25994
|
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
25774
25995
|
}
|
|
25775
25996
|
function readUserSettings(base) {
|
|
25776
|
-
const record2 = readJson(
|
|
25997
|
+
const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
|
|
25777
25998
|
if (!record2) return defaultWorkspaceSettings();
|
|
25778
25999
|
try {
|
|
25779
26000
|
return WorkspaceSettings.parse(record2);
|
|
@@ -25784,13 +26005,17 @@ function readUserSettings(base) {
|
|
|
25784
26005
|
function readJson(file2) {
|
|
25785
26006
|
let text;
|
|
25786
26007
|
try {
|
|
25787
|
-
text =
|
|
26008
|
+
text = readFileSync5(file2, "utf8");
|
|
25788
26009
|
} catch {
|
|
25789
26010
|
return null;
|
|
25790
26011
|
}
|
|
25791
26012
|
return parseJsonObject(text) ?? null;
|
|
25792
26013
|
}
|
|
25793
26014
|
|
|
26015
|
+
// ../../packages/persistence/src/store-symlinks.ts
|
|
26016
|
+
import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
26017
|
+
import { dirname as dirname2, join as join7, resolve } from "path";
|
|
26018
|
+
|
|
25794
26019
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
25795
26020
|
import {
|
|
25796
26021
|
createCipheriv,
|
|
@@ -25902,8 +26127,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
25902
26127
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25903
26128
|
import { execFileSync } from "child_process";
|
|
25904
26129
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
25905
|
-
import { chmodSync as
|
|
25906
|
-
import { join as
|
|
26130
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
26131
|
+
import { join as join8 } from "path";
|
|
25907
26132
|
var VAULT_OCCUPANT_REASON = {
|
|
25908
26133
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25909
26134
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -25922,11 +26147,11 @@ var KEY_MATERIAL_BYTES2 = 32;
|
|
|
25922
26147
|
var KEYCHAIN_SERVICE = "aka-vault";
|
|
25923
26148
|
var KEYCHAIN_ACCOUNT = "keyring";
|
|
25924
26149
|
function parseKeyring(raw) {
|
|
25925
|
-
const
|
|
25926
|
-
if (typeof
|
|
26150
|
+
const parsed2 = JSON.parse(raw);
|
|
26151
|
+
if (typeof parsed2 !== "object" || parsed2 === null) {
|
|
25927
26152
|
throw new Error("vault key file is corrupt: not a JSON object");
|
|
25928
26153
|
}
|
|
25929
|
-
const { current, keys } =
|
|
26154
|
+
const { current, keys } = parsed2;
|
|
25930
26155
|
if (typeof current !== "number" || !Number.isInteger(current) || current < 1) {
|
|
25931
26156
|
throw new Error("vault key file is corrupt: bad current version");
|
|
25932
26157
|
}
|
|
@@ -26002,28 +26227,28 @@ function claimRotationLock(lock, owner) {
|
|
|
26002
26227
|
throw asError(err);
|
|
26003
26228
|
}
|
|
26004
26229
|
try {
|
|
26005
|
-
writeFileSync3(
|
|
26230
|
+
writeFileSync3(join8(lock, LOCK_OWNER_FILE), `${owner}
|
|
26006
26231
|
`, { mode: DATA_FILE_MODE });
|
|
26007
26232
|
return true;
|
|
26008
26233
|
} catch (err) {
|
|
26009
|
-
|
|
26234
|
+
rmSync5(lock, { recursive: true, force: true });
|
|
26010
26235
|
throw asError(err);
|
|
26011
26236
|
}
|
|
26012
26237
|
}
|
|
26013
26238
|
function acquireRotationLock(keysDir2) {
|
|
26014
|
-
const lock =
|
|
26239
|
+
const lock = join8(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
26015
26240
|
const owner = randomBytes2(16).toString("hex");
|
|
26016
26241
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
26017
26242
|
let held;
|
|
26018
26243
|
try {
|
|
26019
|
-
held =
|
|
26244
|
+
held = statSync5(lock);
|
|
26020
26245
|
} catch {
|
|
26021
26246
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26022
26247
|
}
|
|
26023
26248
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
26024
26249
|
const aside = `${lock}.stale.${owner}`;
|
|
26025
26250
|
try {
|
|
26026
|
-
const now =
|
|
26251
|
+
const now = statSync5(lock);
|
|
26027
26252
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
26028
26253
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26029
26254
|
}
|
|
@@ -26032,17 +26257,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
26032
26257
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
26033
26258
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
26034
26259
|
}
|
|
26035
|
-
|
|
26260
|
+
rmSync5(aside, { recursive: true, force: true });
|
|
26036
26261
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
26037
26262
|
return { lock, owner };
|
|
26038
26263
|
}
|
|
26039
26264
|
function releaseRotationLock(lease) {
|
|
26040
26265
|
try {
|
|
26041
|
-
if (
|
|
26266
|
+
if (readFileSync6(join8(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
26042
26267
|
} catch {
|
|
26043
26268
|
return;
|
|
26044
26269
|
}
|
|
26045
|
-
|
|
26270
|
+
rmSync5(lease.lock, { recursive: true, force: true });
|
|
26046
26271
|
}
|
|
26047
26272
|
function withRotationLock(keysDir2, work) {
|
|
26048
26273
|
ensureDataDirSync(keysDir2);
|
|
@@ -26059,7 +26284,7 @@ var FileKeyProvider = class {
|
|
|
26059
26284
|
this.#keysDir = keysDir2;
|
|
26060
26285
|
}
|
|
26061
26286
|
get filePath() {
|
|
26062
|
-
return
|
|
26287
|
+
return join8(this.#keysDir, VAULT_KEY_FILENAME);
|
|
26063
26288
|
}
|
|
26064
26289
|
loadOrCreate() {
|
|
26065
26290
|
return asAsync(() => {
|
|
@@ -26089,7 +26314,7 @@ var FileKeyProvider = class {
|
|
|
26089
26314
|
#read() {
|
|
26090
26315
|
let raw;
|
|
26091
26316
|
try {
|
|
26092
|
-
raw =
|
|
26317
|
+
raw = readFileSync6(this.filePath, "utf8");
|
|
26093
26318
|
} catch (err) {
|
|
26094
26319
|
if (err.code === "ENOENT") return null;
|
|
26095
26320
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -26146,7 +26371,7 @@ var FileKeyProvider = class {
|
|
|
26146
26371
|
};
|
|
26147
26372
|
function tightenFileMode(file2) {
|
|
26148
26373
|
try {
|
|
26149
|
-
|
|
26374
|
+
chmodSync3(file2, DATA_FILE_MODE);
|
|
26150
26375
|
} catch {
|
|
26151
26376
|
}
|
|
26152
26377
|
}
|
|
@@ -26411,25 +26636,25 @@ var SecretVault = class {
|
|
|
26411
26636
|
* model. Every call that gets as far as an identified row writes an audit row.
|
|
26412
26637
|
*/
|
|
26413
26638
|
async detokenize(token, opts) {
|
|
26414
|
-
const
|
|
26415
|
-
if (!
|
|
26639
|
+
const parsed2 = parsePointer(token);
|
|
26640
|
+
if (!parsed2) return UNAVAILABLE;
|
|
26416
26641
|
let signKey;
|
|
26417
26642
|
try {
|
|
26418
|
-
const epoch = await this.#keys.materialFor(
|
|
26643
|
+
const epoch = await this.#keys.materialFor(parsed2.keyVersion);
|
|
26419
26644
|
signKey = deriveSubkeys(epoch.material).sign;
|
|
26420
26645
|
} catch {
|
|
26421
26646
|
return UNAVAILABLE;
|
|
26422
26647
|
}
|
|
26423
|
-
if (!verifyPointerTag(signKey,
|
|
26648
|
+
if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
|
|
26424
26649
|
return UNAVAILABLE;
|
|
26425
26650
|
}
|
|
26426
|
-
const pointerId = base32Encode(
|
|
26651
|
+
const pointerId = base32Encode(parsed2.pointerId);
|
|
26427
26652
|
const row = this.#repo.byPointerId(pointerId);
|
|
26428
26653
|
if (!row) {
|
|
26429
26654
|
this.#audit(pointerId, opts, "unavailable");
|
|
26430
26655
|
return UNAVAILABLE;
|
|
26431
26656
|
}
|
|
26432
|
-
if (row.category !==
|
|
26657
|
+
if (row.category !== parsed2.category) return UNAVAILABLE;
|
|
26433
26658
|
if (opts.target === "model") {
|
|
26434
26659
|
const grantId = opts.grantId;
|
|
26435
26660
|
const verify = this.#verifyGrant;
|
|
@@ -26466,7 +26691,7 @@ var SecretVault = class {
|
|
|
26466
26691
|
// moved the epoch past the one this token names, and a format bump may
|
|
26467
26692
|
// have moved the constant past the generation this row was sealed
|
|
26468
26693
|
// under — the AAD follows the row in both cases, never the token.
|
|
26469
|
-
bindingInput(row.keyVersion,
|
|
26694
|
+
bindingInput(row.keyVersion, parsed2.pointerId, row.category, row.formatVersion)
|
|
26470
26695
|
);
|
|
26471
26696
|
} catch {
|
|
26472
26697
|
raw = null;
|
|
@@ -26684,19 +26909,19 @@ var SecretVault = class {
|
|
|
26684
26909
|
// preview. Verifying needs the historical epoch's key, which is why these
|
|
26685
26910
|
// surfaces are async.
|
|
26686
26911
|
async #rowFor(token) {
|
|
26687
|
-
const
|
|
26688
|
-
if (!
|
|
26912
|
+
const parsed2 = parsePointer(token);
|
|
26913
|
+
if (!parsed2) return null;
|
|
26689
26914
|
try {
|
|
26690
|
-
const epoch = await this.#keys.materialFor(
|
|
26915
|
+
const epoch = await this.#keys.materialFor(parsed2.keyVersion);
|
|
26691
26916
|
const signKey = deriveSubkeys(epoch.material).sign;
|
|
26692
|
-
if (!verifyPointerTag(signKey,
|
|
26917
|
+
if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
|
|
26693
26918
|
return null;
|
|
26694
26919
|
}
|
|
26695
26920
|
} catch {
|
|
26696
26921
|
return null;
|
|
26697
26922
|
}
|
|
26698
|
-
const row = this.#repo.byPointerId(base32Encode(
|
|
26699
|
-
if (row?.category !==
|
|
26923
|
+
const row = this.#repo.byPointerId(base32Encode(parsed2.pointerId));
|
|
26924
|
+
if (row?.category !== parsed2.category) return null;
|
|
26700
26925
|
return row;
|
|
26701
26926
|
}
|
|
26702
26927
|
#audit(pointerId, opts, outcome) {
|
|
@@ -26716,13 +26941,13 @@ var SecretVault = class {
|
|
|
26716
26941
|
};
|
|
26717
26942
|
|
|
26718
26943
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
26719
|
-
import { existsSync as
|
|
26720
|
-
import { join as
|
|
26944
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
26945
|
+
import { join as join9 } from "path";
|
|
26721
26946
|
var MARKER = "warn-era-capped";
|
|
26722
26947
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
26723
26948
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
26724
|
-
const marker =
|
|
26725
|
-
if (
|
|
26949
|
+
const marker = join9(dataDir2, MARKER);
|
|
26950
|
+
if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
|
|
26726
26951
|
const capped = db.policies.capCategoryActions();
|
|
26727
26952
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
26728
26953
|
`, { mode: DATA_FILE_MODE });
|
|
@@ -26763,8 +26988,8 @@ function hostOf(url2) {
|
|
|
26763
26988
|
}
|
|
26764
26989
|
}
|
|
26765
26990
|
function resolveProvider() {
|
|
26766
|
-
const
|
|
26767
|
-
const env =
|
|
26991
|
+
const parsed2 = ProviderEnvSchema.safeParse(process.env);
|
|
26992
|
+
const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
|
|
26768
26993
|
if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
|
|
26769
26994
|
if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
|
|
26770
26995
|
const baseUrl = env.ANTHROPIC_BASE_URL;
|
|
@@ -26791,8 +27016,8 @@ function providerFromModelId(modelId) {
|
|
|
26791
27016
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
26792
27017
|
try {
|
|
26793
27018
|
ensureLayoutDirSync(base);
|
|
26794
|
-
const settingsFile =
|
|
26795
|
-
if (
|
|
27019
|
+
const settingsFile = join10(settingsDir(base), "settings.json");
|
|
27020
|
+
if (existsSync6(settingsFile)) tightenFile(settingsFile);
|
|
26796
27021
|
} catch {
|
|
26797
27022
|
}
|
|
26798
27023
|
migrateLegacyLayout(base);
|
|
@@ -26815,9 +27040,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
26815
27040
|
}
|
|
26816
27041
|
|
|
26817
27042
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
26818
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
27043
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
26819
27044
|
import { homedir as homedir2 } from "os";
|
|
26820
|
-
import { basename as basename3, join as
|
|
27045
|
+
import { basename as basename3, join as join12 } from "path";
|
|
26821
27046
|
|
|
26822
27047
|
// ../../packages/detections/src/egress/registry.ts
|
|
26823
27048
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -28594,10 +28819,10 @@ var localhost_ref_default = {
|
|
|
28594
28819
|
severity: "low",
|
|
28595
28820
|
matcher: {
|
|
28596
28821
|
type: "regex",
|
|
28597
|
-
pattern: "
|
|
28822
|
+
pattern: "(?<![A-Za-z0-9_])(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|(?<![A-Za-z0-9_]\\[)::1(?!:|\\.[0-9]))(?![A-Za-z0-9_])",
|
|
28598
28823
|
flags: "g"
|
|
28599
28824
|
},
|
|
28600
|
-
examples: ["localhost", "127.0.0.1"]
|
|
28825
|
+
examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
|
|
28601
28826
|
};
|
|
28602
28827
|
|
|
28603
28828
|
// ../../rules/core-code-context/stack-trace.json
|
|
@@ -29948,8 +30173,8 @@ function scanText(text, ruleVersions) {
|
|
|
29948
30173
|
}
|
|
29949
30174
|
|
|
29950
30175
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
29951
|
-
import { existsSync as
|
|
29952
|
-
import { basename as basename2, dirname as
|
|
30176
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
|
|
30177
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join11, sep as sep2 } from "path";
|
|
29953
30178
|
function resolveRepoIdentity(cwd) {
|
|
29954
30179
|
try {
|
|
29955
30180
|
const root = findGitRoot(cwd);
|
|
@@ -29982,36 +30207,36 @@ function resolveRepoNwo(cwd) {
|
|
|
29982
30207
|
function findGitRoot(start) {
|
|
29983
30208
|
let dir = start;
|
|
29984
30209
|
for (; ; ) {
|
|
29985
|
-
if (
|
|
29986
|
-
const parent =
|
|
30210
|
+
if (existsSync7(join11(dir, ".git"))) return dir;
|
|
30211
|
+
const parent = dirname3(dir);
|
|
29987
30212
|
if (parent === dir) return void 0;
|
|
29988
30213
|
dir = parent;
|
|
29989
30214
|
}
|
|
29990
30215
|
}
|
|
29991
30216
|
function resolveGitContext(root) {
|
|
29992
|
-
const dotGit =
|
|
30217
|
+
const dotGit = join11(root, ".git");
|
|
29993
30218
|
try {
|
|
29994
|
-
if (
|
|
29995
|
-
return { configPath:
|
|
30219
|
+
if (statSync6(dotGit).isDirectory()) {
|
|
30220
|
+
return { configPath: join11(dotGit, "config"), headRoot: root };
|
|
29996
30221
|
}
|
|
29997
30222
|
} catch {
|
|
29998
30223
|
return void 0;
|
|
29999
30224
|
}
|
|
30000
30225
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
30001
30226
|
if (!target) return void 0;
|
|
30002
|
-
const gitdir = isAbsolute(target) ? target :
|
|
30003
|
-
if (
|
|
30004
|
-
return { configPath:
|
|
30227
|
+
const gitdir = isAbsolute(target) ? target : join11(root, target);
|
|
30228
|
+
if (existsSync7(join11(gitdir, "config"))) {
|
|
30229
|
+
return { configPath: join11(gitdir, "config"), headRoot: root };
|
|
30005
30230
|
}
|
|
30006
|
-
const commonRaw = safeRead(
|
|
30231
|
+
const commonRaw = safeRead(join11(gitdir, "commondir"))?.trim();
|
|
30007
30232
|
if (!commonRaw) return void 0;
|
|
30008
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
30009
|
-
const headRoot = basename2(commonGitDir) === ".git" ?
|
|
30010
|
-
return { configPath:
|
|
30233
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join11(gitdir, commonRaw);
|
|
30234
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
|
|
30235
|
+
return { configPath: join11(commonGitDir, "config"), headRoot };
|
|
30011
30236
|
}
|
|
30012
30237
|
function safeRead(path) {
|
|
30013
30238
|
try {
|
|
30014
|
-
return
|
|
30239
|
+
return readFileSync7(path, "utf8");
|
|
30015
30240
|
} catch {
|
|
30016
30241
|
return void 0;
|
|
30017
30242
|
}
|
|
@@ -30085,7 +30310,7 @@ function buildIngestEvent(input) {
|
|
|
30085
30310
|
}
|
|
30086
30311
|
|
|
30087
30312
|
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
30088
|
-
import { existsSync as
|
|
30313
|
+
import { existsSync as existsSync8 } from "fs";
|
|
30089
30314
|
import { fileURLToPath } from "url";
|
|
30090
30315
|
import { Worker } from "worker_threads";
|
|
30091
30316
|
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
@@ -30099,7 +30324,7 @@ function resolveWorkerUrl() {
|
|
|
30099
30324
|
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
30100
30325
|
const candidate = new URL(name, import.meta.url);
|
|
30101
30326
|
try {
|
|
30102
|
-
if (
|
|
30327
|
+
if (existsSync8(fileURLToPath(candidate))) {
|
|
30103
30328
|
resolvedWorkerUrl = candidate;
|
|
30104
30329
|
return candidate;
|
|
30105
30330
|
}
|
|
@@ -30284,8 +30509,8 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30284
30509
|
}
|
|
30285
30510
|
function enqueue(spec) {
|
|
30286
30511
|
const next = chain.then(
|
|
30287
|
-
() => new Promise((
|
|
30288
|
-
spec(
|
|
30512
|
+
() => new Promise((resolve3) => {
|
|
30513
|
+
spec(resolve3);
|
|
30289
30514
|
})
|
|
30290
30515
|
);
|
|
30291
30516
|
chain = next.then(
|
|
@@ -30296,7 +30521,7 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30296
30521
|
}
|
|
30297
30522
|
return {
|
|
30298
30523
|
scan(text, context, scanOpts) {
|
|
30299
|
-
return enqueue((
|
|
30524
|
+
return enqueue((resolve3) => {
|
|
30300
30525
|
runOne(
|
|
30301
30526
|
{
|
|
30302
30527
|
budgetMs,
|
|
@@ -30309,23 +30534,23 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30309
30534
|
}),
|
|
30310
30535
|
reply: (message) => {
|
|
30311
30536
|
if (message.kind !== "result") return false;
|
|
30312
|
-
|
|
30537
|
+
resolve3({ status: "ok", findings: message.findings });
|
|
30313
30538
|
return true;
|
|
30314
30539
|
}
|
|
30315
30540
|
},
|
|
30316
|
-
|
|
30541
|
+
resolve3
|
|
30317
30542
|
);
|
|
30318
30543
|
});
|
|
30319
30544
|
},
|
|
30320
30545
|
probe(rule) {
|
|
30321
|
-
return enqueue((
|
|
30546
|
+
return enqueue((resolve3) => {
|
|
30322
30547
|
runOne(
|
|
30323
30548
|
{
|
|
30324
30549
|
budgetMs: probeBudgetMs,
|
|
30325
30550
|
build: (id) => ({ kind: "probe", id, rule }),
|
|
30326
30551
|
reply: (message) => {
|
|
30327
30552
|
if (message.kind !== "probed") return false;
|
|
30328
|
-
|
|
30553
|
+
resolve3({
|
|
30329
30554
|
status: "ok",
|
|
30330
30555
|
verdict: message.verdict,
|
|
30331
30556
|
worstMs: message.worstMs,
|
|
@@ -30334,7 +30559,7 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30334
30559
|
return true;
|
|
30335
30560
|
}
|
|
30336
30561
|
},
|
|
30337
|
-
|
|
30562
|
+
resolve3
|
|
30338
30563
|
);
|
|
30339
30564
|
});
|
|
30340
30565
|
},
|
|
@@ -30564,8 +30789,8 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
30564
30789
|
|
|
30565
30790
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
30566
30791
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
30567
|
-
import { readFileSync as
|
|
30568
|
-
import { join as
|
|
30792
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
30793
|
+
import { join as join13 } from "path";
|
|
30569
30794
|
|
|
30570
30795
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
30571
30796
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -30597,16 +30822,16 @@ function resolveInventoryContext(input) {
|
|
|
30597
30822
|
}
|
|
30598
30823
|
|
|
30599
30824
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
30600
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
30601
|
-
import { join as
|
|
30825
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
30826
|
+
import { join as join14 } from "path";
|
|
30602
30827
|
|
|
30603
30828
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
30604
|
-
import { readdirSync as readdirSync3, realpathSync as
|
|
30605
|
-
import { basename as basename4, dirname as
|
|
30829
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
30830
|
+
import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
|
|
30606
30831
|
|
|
30607
30832
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
30608
|
-
import { existsSync as
|
|
30609
|
-
import { basename as basename5, join as
|
|
30833
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
30834
|
+
import { basename as basename5, join as join15 } from "path";
|
|
30610
30835
|
|
|
30611
30836
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30612
30837
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -31017,8 +31242,8 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
31017
31242
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
31018
31243
|
|
|
31019
31244
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
31020
|
-
import { mkdirSync as mkdirSync3, statSync as
|
|
31021
|
-
import { join as
|
|
31245
|
+
import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
31246
|
+
import { join as join16 } from "path";
|
|
31022
31247
|
|
|
31023
31248
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
31024
31249
|
function redactedPlaceholder(category) {
|
|
@@ -31322,7 +31547,7 @@ var UNOPENABLE_VAULT = {
|
|
|
31322
31547
|
|
|
31323
31548
|
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
31324
31549
|
import { writeFileSync as writeFileSync7 } from "fs";
|
|
31325
|
-
import { join as
|
|
31550
|
+
import { join as join17 } from "path";
|
|
31326
31551
|
|
|
31327
31552
|
// ../../packages/setup-wizard/src/triage/merge.ts
|
|
31328
31553
|
var RANK = Object.fromEntries(
|
|
@@ -31330,9 +31555,9 @@ var RANK = Object.fromEntries(
|
|
|
31330
31555
|
);
|
|
31331
31556
|
|
|
31332
31557
|
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
31333
|
-
import { mkdtempSync, readFileSync as
|
|
31558
|
+
import { mkdtempSync, readFileSync as readFileSync11, rmdirSync, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
31334
31559
|
import { tmpdir } from "os";
|
|
31335
|
-
import { basename as basename6, dirname as
|
|
31560
|
+
import { basename as basename6, dirname as dirname5, join as join18 } from "path";
|
|
31336
31561
|
var SuppressionEntrySchema = external_exports.object({
|
|
31337
31562
|
ruleId: external_exports.string(),
|
|
31338
31563
|
category: DetectionCategory,
|
|
@@ -31376,8 +31601,1198 @@ var PersistedPlanSchema = external_exports.object({
|
|
|
31376
31601
|
// ../../packages/setup-wizard/src/triage/writeback.ts
|
|
31377
31602
|
var TRIAGE_STATUSES = ["complete", "complete:no-history", "skipped:no-consent"];
|
|
31378
31603
|
|
|
31379
|
-
// ../../packages/plugin-runtime/src/
|
|
31604
|
+
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
31605
|
+
function statusOf(err) {
|
|
31606
|
+
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
31607
|
+
const { status } = err;
|
|
31608
|
+
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
31609
|
+
return status >= 100 && status <= 599 ? status : null;
|
|
31610
|
+
}
|
|
31611
|
+
function classifyFailure(err) {
|
|
31612
|
+
switch (statusOf(err)) {
|
|
31613
|
+
case 401:
|
|
31614
|
+
return "unauthorized";
|
|
31615
|
+
case 403:
|
|
31616
|
+
return "forbidden";
|
|
31617
|
+
default:
|
|
31618
|
+
return "unreachable";
|
|
31619
|
+
}
|
|
31620
|
+
}
|
|
31621
|
+
|
|
31622
|
+
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
31623
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
31624
|
+
import { join as join19 } from "path";
|
|
31625
|
+
var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
31626
|
+
function forwardDropsPath(dataDir2) {
|
|
31627
|
+
return join19(dataDir2, FORWARD_DROPS_FILENAME);
|
|
31628
|
+
}
|
|
31629
|
+
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
31630
|
+
if (count <= 0) return;
|
|
31631
|
+
try {
|
|
31632
|
+
ensureDataDirSync(dataDir2);
|
|
31633
|
+
const previous = readForwardDrops(dataDir2);
|
|
31634
|
+
const next = {
|
|
31635
|
+
droppedForwards: (previous?.droppedForwards ?? 0) + count,
|
|
31636
|
+
lastDropAtMs: nowMs
|
|
31637
|
+
};
|
|
31638
|
+
writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
|
|
31639
|
+
`);
|
|
31640
|
+
} catch {
|
|
31641
|
+
}
|
|
31642
|
+
}
|
|
31643
|
+
function readForwardDrops(dataDir2) {
|
|
31644
|
+
try {
|
|
31645
|
+
const parsed2 = JSON.parse(readFileSync12(forwardDropsPath(dataDir2), "utf8"));
|
|
31646
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
31647
|
+
const record2 = parsed2;
|
|
31648
|
+
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
31649
|
+
return null;
|
|
31650
|
+
}
|
|
31651
|
+
if (record2.droppedForwards <= 0) return null;
|
|
31652
|
+
if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
|
|
31653
|
+
return null;
|
|
31654
|
+
}
|
|
31655
|
+
return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
|
|
31656
|
+
} catch {
|
|
31657
|
+
return null;
|
|
31658
|
+
}
|
|
31659
|
+
}
|
|
31660
|
+
|
|
31661
|
+
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
31380
31662
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
31663
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
31664
|
+
import { readFile, rename, writeFile } from "fs/promises";
|
|
31665
|
+
import { join as join20 } from "path";
|
|
31666
|
+
|
|
31667
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
31668
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
31669
|
+
function withTimeout(promise2, ms) {
|
|
31670
|
+
let timer;
|
|
31671
|
+
const timeout = new Promise((_, reject) => {
|
|
31672
|
+
timer = setTimeout(() => {
|
|
31673
|
+
reject(new Error("attached gateway request timed out"));
|
|
31674
|
+
}, ms);
|
|
31675
|
+
});
|
|
31676
|
+
promise2.catch(() => void 0);
|
|
31677
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
31678
|
+
clearTimeout(timer);
|
|
31679
|
+
});
|
|
31680
|
+
}
|
|
31681
|
+
|
|
31682
|
+
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
31683
|
+
function isInvalidRequest(err) {
|
|
31684
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
31685
|
+
}
|
|
31686
|
+
var FORWARD_BUDGET_MS = 1500;
|
|
31687
|
+
var DECISION_PATH_BUDGET_MS = 800;
|
|
31688
|
+
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
31689
|
+
var BREAKER_COOLDOWN_MS = 3e4;
|
|
31690
|
+
var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
|
|
31691
|
+
var FAILURES = /* @__PURE__ */ new Set([
|
|
31692
|
+
"unauthorized",
|
|
31693
|
+
"forbidden",
|
|
31694
|
+
"unreachable"
|
|
31695
|
+
]);
|
|
31696
|
+
var FORWARD_STATE_FILENAME = "attached-state.json";
|
|
31697
|
+
var STATE_FILENAME = FORWARD_STATE_FILENAME;
|
|
31698
|
+
function parseBreakerState(raw, nowMs) {
|
|
31699
|
+
try {
|
|
31700
|
+
const parsed2 = JSON.parse(raw);
|
|
31701
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
31702
|
+
const record2 = parsed2;
|
|
31703
|
+
const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
|
|
31704
|
+
const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
|
|
31705
|
+
const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
|
|
31706
|
+
return { consecutiveFailures: failures, openedAtMs, lastFailure };
|
|
31707
|
+
} catch {
|
|
31708
|
+
return null;
|
|
31709
|
+
}
|
|
31710
|
+
}
|
|
31711
|
+
function createForwardPolicy(deps) {
|
|
31712
|
+
const now = deps.now ?? (() => Date.now());
|
|
31713
|
+
const file2 = join20(deps.dir, STATE_FILENAME);
|
|
31714
|
+
let state = null;
|
|
31715
|
+
let loading = null;
|
|
31716
|
+
async function readState() {
|
|
31717
|
+
let raw;
|
|
31718
|
+
try {
|
|
31719
|
+
raw = await readFile(file2, "utf8");
|
|
31720
|
+
} catch {
|
|
31721
|
+
return { ...CLOSED };
|
|
31722
|
+
}
|
|
31723
|
+
return parseBreakerState(raw, now()) ?? { ...CLOSED };
|
|
31724
|
+
}
|
|
31725
|
+
async function load() {
|
|
31726
|
+
if (state !== null) return state;
|
|
31727
|
+
loading ??= readState().then((loaded) => {
|
|
31728
|
+
state = loaded;
|
|
31729
|
+
loading = null;
|
|
31730
|
+
return loaded;
|
|
31731
|
+
});
|
|
31732
|
+
return loading;
|
|
31733
|
+
}
|
|
31734
|
+
async function persist(next) {
|
|
31735
|
+
state = next;
|
|
31736
|
+
try {
|
|
31737
|
+
await ensureDataDir(deps.dir);
|
|
31738
|
+
const tmp = `${file2}.${randomUUID15()}.tmp`;
|
|
31739
|
+
await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
31740
|
+
await rename(tmp, file2);
|
|
31741
|
+
} catch {
|
|
31742
|
+
}
|
|
31743
|
+
}
|
|
31744
|
+
return {
|
|
31745
|
+
async run(op, opts) {
|
|
31746
|
+
const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
|
|
31747
|
+
let current;
|
|
31748
|
+
try {
|
|
31749
|
+
current = await load();
|
|
31750
|
+
} catch {
|
|
31751
|
+
current = { ...CLOSED };
|
|
31752
|
+
}
|
|
31753
|
+
const at = now();
|
|
31754
|
+
if (current.openedAtMs !== null) {
|
|
31755
|
+
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
31756
|
+
return { ok: false, reason: "breaker-open" };
|
|
31757
|
+
}
|
|
31758
|
+
await persist({
|
|
31759
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
31760
|
+
openedAtMs: at,
|
|
31761
|
+
lastFailure: current.lastFailure
|
|
31762
|
+
});
|
|
31763
|
+
}
|
|
31764
|
+
try {
|
|
31765
|
+
const value = await withTimeout(op(), budget);
|
|
31766
|
+
if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
|
|
31767
|
+
await persist({ ...CLOSED });
|
|
31768
|
+
}
|
|
31769
|
+
return { ok: true, value };
|
|
31770
|
+
} catch (err) {
|
|
31771
|
+
if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
|
|
31772
|
+
const reason = classifyFailure(err);
|
|
31773
|
+
const failures = current.consecutiveFailures + 1;
|
|
31774
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
31775
|
+
await persist({
|
|
31776
|
+
consecutiveFailures: failures,
|
|
31777
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
31778
|
+
lastFailure: reason
|
|
31779
|
+
});
|
|
31780
|
+
return { ok: false, reason };
|
|
31781
|
+
}
|
|
31782
|
+
}
|
|
31783
|
+
};
|
|
31784
|
+
}
|
|
31785
|
+
|
|
31786
|
+
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
31787
|
+
var ACTION_STRENGTH = {
|
|
31788
|
+
allow: 0,
|
|
31789
|
+
log: 1,
|
|
31790
|
+
warn: 2,
|
|
31791
|
+
redact: 3,
|
|
31792
|
+
block: 4
|
|
31793
|
+
};
|
|
31794
|
+
function ruleCategoryMap(wireRules, localRules) {
|
|
31795
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
31796
|
+
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
31797
|
+
for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
|
|
31798
|
+
for (const pack of bundledDetections()) {
|
|
31799
|
+
for (const rule of pack.rules) map2.set(rule.id, rule.category);
|
|
31800
|
+
}
|
|
31801
|
+
return map2;
|
|
31802
|
+
}
|
|
31803
|
+
function strongerOf(a, b) {
|
|
31804
|
+
if (a === null) return b;
|
|
31805
|
+
if (b === null) return a;
|
|
31806
|
+
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
31807
|
+
}
|
|
31808
|
+
function policyKey(policy) {
|
|
31809
|
+
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
31810
|
+
}
|
|
31811
|
+
function floorFor(policy, categoryByRuleId) {
|
|
31812
|
+
const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
|
|
31813
|
+
return category === void 0 ? null : DEFAULT_ACTIONS[category];
|
|
31814
|
+
}
|
|
31815
|
+
function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
31816
|
+
const merged = /* @__PURE__ */ new Map();
|
|
31817
|
+
const disabled = [];
|
|
31818
|
+
const remoteCategoryAction = /* @__PURE__ */ new Map();
|
|
31819
|
+
for (const policy of remotePolicies) {
|
|
31820
|
+
if (!policy.enabled) continue;
|
|
31821
|
+
if (!("category" in policy.target)) continue;
|
|
31822
|
+
if (remoteCategoryAction.has(policy.target.category)) continue;
|
|
31823
|
+
const floor = floorFor(policy, categoryByRuleId);
|
|
31824
|
+
remoteCategoryAction.set(
|
|
31825
|
+
policy.target.category,
|
|
31826
|
+
floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
|
|
31827
|
+
);
|
|
31828
|
+
}
|
|
31829
|
+
for (const policy of localPolicies) {
|
|
31830
|
+
if (!policy.enabled) {
|
|
31831
|
+
disabled.push(policy);
|
|
31832
|
+
continue;
|
|
31833
|
+
}
|
|
31834
|
+
const key = policyKey(policy);
|
|
31835
|
+
if (merged.has(key)) continue;
|
|
31836
|
+
let remoteFloor = null;
|
|
31837
|
+
if ("ruleId" in policy.target) {
|
|
31838
|
+
const category = categoryByRuleId.get(policy.target.ruleId);
|
|
31839
|
+
if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
|
|
31840
|
+
}
|
|
31841
|
+
merged.set(
|
|
31842
|
+
key,
|
|
31843
|
+
remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
|
|
31844
|
+
);
|
|
31845
|
+
}
|
|
31846
|
+
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
31847
|
+
for (const policy of merged.values()) {
|
|
31848
|
+
if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
|
|
31849
|
+
}
|
|
31850
|
+
for (const policy of remotePolicies) {
|
|
31851
|
+
if (!policy.enabled) {
|
|
31852
|
+
disabled.push(policy);
|
|
31853
|
+
continue;
|
|
31854
|
+
}
|
|
31855
|
+
const key = policyKey(policy);
|
|
31856
|
+
const floor = floorFor(policy, categoryByRuleId);
|
|
31857
|
+
let localFloor = null;
|
|
31858
|
+
if ("ruleId" in policy.target) {
|
|
31859
|
+
const category = categoryByRuleId.get(policy.target.ruleId);
|
|
31860
|
+
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
31861
|
+
}
|
|
31862
|
+
const effectiveFloor = strongerOf(floor, localFloor);
|
|
31863
|
+
const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
|
|
31864
|
+
const existing = merged.get(key);
|
|
31865
|
+
if (existing === void 0) {
|
|
31866
|
+
merged.set(key, clamped);
|
|
31867
|
+
continue;
|
|
31868
|
+
}
|
|
31869
|
+
if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
|
|
31870
|
+
merged.set(key, clamped);
|
|
31871
|
+
}
|
|
31872
|
+
}
|
|
31873
|
+
return [...merged.values(), ...disabled];
|
|
31874
|
+
}
|
|
31875
|
+
var AttachedDataGateway = class {
|
|
31876
|
+
constructor(deps) {
|
|
31877
|
+
this.deps = deps;
|
|
31878
|
+
}
|
|
31879
|
+
deps;
|
|
31880
|
+
/**
|
|
31881
|
+
* The control plane's OWN resolution of this session's inventory, captured by
|
|
31882
|
+
* ensureInventory. Null until the first successful forward — and it stays
|
|
31883
|
+
* null for the whole session when the control plane is unreachable, which is fine:
|
|
31884
|
+
* reKeyForForward then leaves the event's ids alone and the control plane resolves
|
|
31885
|
+
* what it can from the descriptors it already has.
|
|
31886
|
+
*/
|
|
31887
|
+
remoteInventory = null;
|
|
31888
|
+
// ---------------------------------------------------------------------
|
|
31889
|
+
// Writes: local first, then forward.
|
|
31890
|
+
// ---------------------------------------------------------------------
|
|
31891
|
+
async recordCapture(record2) {
|
|
31892
|
+
await this.deps.local.recordCapture(record2);
|
|
31893
|
+
await this.deps.forward.run(
|
|
31894
|
+
() => this.deps.client.ingestEvents({
|
|
31895
|
+
events: [record2.event],
|
|
31896
|
+
...record2.dedupe ? { dedupe: record2.dedupe } : {}
|
|
31897
|
+
}),
|
|
31898
|
+
{ decisionPath: true }
|
|
31899
|
+
);
|
|
31900
|
+
}
|
|
31901
|
+
async ensureInventory(ctx) {
|
|
31902
|
+
const resolved = await this.deps.local.ensureInventory(ctx);
|
|
31903
|
+
const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
|
|
31904
|
+
this.remoteInventory = remote.ok ? remote.value : null;
|
|
31905
|
+
const snapshot = await (async () => {
|
|
31906
|
+
try {
|
|
31907
|
+
return await this.deps.posture?.prepare() ?? null;
|
|
31908
|
+
} catch {
|
|
31909
|
+
return null;
|
|
31910
|
+
}
|
|
31911
|
+
})();
|
|
31912
|
+
if (snapshot) {
|
|
31913
|
+
try {
|
|
31914
|
+
await withTimeout(
|
|
31915
|
+
this.deps.posture?.send(snapshot) ?? Promise.resolve(),
|
|
31916
|
+
REQUEST_TIMEOUT_MS
|
|
31917
|
+
);
|
|
31918
|
+
} catch {
|
|
31919
|
+
}
|
|
31920
|
+
}
|
|
31921
|
+
return resolved;
|
|
31922
|
+
}
|
|
31923
|
+
// The id is minted CLIENT-side and stored verbatim: the control plane does NOT
|
|
31924
|
+
// re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
|
|
31925
|
+
// its own scoping columns, so the device and the forwarded copy
|
|
31926
|
+
// share one id space — which is what makes a re-post idempotent at all.
|
|
31927
|
+
//
|
|
31928
|
+
// Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
|
|
31929
|
+
// `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
|
|
31930
|
+
// what makes an attached retry safe: a capture-stubbed session row can still
|
|
31931
|
+
// be HEALED by the authoritative root, while a duplicate non-session event —
|
|
31932
|
+
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
31933
|
+
async recordAuditEvent(event) {
|
|
31934
|
+
await this.deps.local.recordAuditEvent(event);
|
|
31935
|
+
await this.deps.forward.run(
|
|
31936
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
31937
|
+
);
|
|
31938
|
+
}
|
|
31939
|
+
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
31940
|
+
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
31941
|
+
// client method yet) by pre-building the audit event from the natural key.
|
|
31942
|
+
// The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
|
|
31943
|
+
// which would write the event to the local store a second time.
|
|
31944
|
+
async recordLlmCall(input) {
|
|
31945
|
+
await this.deps.local.recordLlmCall(input);
|
|
31946
|
+
await this.deps.forward.run(
|
|
31947
|
+
() => this.deps.client.recordAuditEvent(
|
|
31948
|
+
reKeyForForward(llmAuditEvent(input), this.remoteInventory)
|
|
31949
|
+
)
|
|
31950
|
+
);
|
|
31951
|
+
}
|
|
31952
|
+
/**
|
|
31953
|
+
* Forward one batch, item by item, under ONE aggregate deadline.
|
|
31954
|
+
*
|
|
31955
|
+
* Per-item budgets bound each request and nothing bounded their sum — see
|
|
31956
|
+
* BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
|
|
31957
|
+
* rather than sent: the local write has already succeeded, so every caller
|
|
31958
|
+
* has a correct result to return, and a drop is the outcome this path is
|
|
31959
|
+
* built to accept (G8) where a blown hook timeout is not.
|
|
31960
|
+
*
|
|
31961
|
+
* Serial rather than concurrent on purpose. Firing N requests at once would
|
|
31962
|
+
* trade a latency problem for a burst the plane's own per-key rate limiting
|
|
31963
|
+
* would answer with the refusals the breaker then counts.
|
|
31964
|
+
*
|
|
31965
|
+
* WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
|
|
31966
|
+
* `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
|
|
31967
|
+
* lets status call the forward unhealthy; this path returns BEFORE `run` is
|
|
31968
|
+
* reached, so without the tally in `forward-drops.ts` a slow-but-answering
|
|
31969
|
+
* plane produces no failures, keeps the breaker closed, renders a healthy
|
|
31970
|
+
* block, and discards the tail of every batch indefinitely.
|
|
31971
|
+
*/
|
|
31972
|
+
async forwardBatch(inputs, toEvent) {
|
|
31973
|
+
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
31974
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
31975
|
+
const now = Date.now();
|
|
31976
|
+
if (now >= deadline) {
|
|
31977
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
31978
|
+
return;
|
|
31979
|
+
}
|
|
31980
|
+
const input = inputs[i];
|
|
31981
|
+
await this.deps.forward.run(
|
|
31982
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
|
|
31983
|
+
);
|
|
31984
|
+
}
|
|
31985
|
+
}
|
|
31986
|
+
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
31987
|
+
// gateway may write the whole batch in one local transaction, and looping
|
|
31988
|
+
// here would replace that with N separate local writes.
|
|
31989
|
+
async recordLlmCalls(inputs) {
|
|
31990
|
+
await this.deps.local.recordLlmCalls(inputs);
|
|
31991
|
+
await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
|
|
31992
|
+
}
|
|
31993
|
+
// `input.inspections` (secrets detected client-side in the tool's masked
|
|
31994
|
+
// target) ride along on the request's `inspections` field — the control plane
|
|
31995
|
+
// persists each as an inspection_findings row linked to this audit event
|
|
31996
|
+
// (see RecordAuditEventRequest in @akasecurity/schema). The masked
|
|
31997
|
+
// `target` already rides `input.attributes`, so no raw secret leaks either
|
|
31998
|
+
// way — this only stops the FINDING row itself from being dropped.
|
|
31999
|
+
async recordToolCalls(inputs) {
|
|
32000
|
+
await this.deps.local.recordToolCalls(inputs);
|
|
32001
|
+
await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
|
|
32002
|
+
}
|
|
32003
|
+
// Forwarded as a `config_scan` audit event: there is no dedicated
|
|
32004
|
+
// config-scan ingest endpoint, and the audit-event door is the one the
|
|
32005
|
+
// control plane already opens for client-minted, idempotent records.
|
|
32006
|
+
//
|
|
32007
|
+
// ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
|
|
32008
|
+
// re-derive the rest. A `ConfigScanRecord` is four things committed together
|
|
32009
|
+
// locally — the inventory `items`, this audit event, and the posture
|
|
32010
|
+
// `definitions`/`findings` that reference it — and three of them stay on the
|
|
32011
|
+
// device. Say that plainly rather than let the asymmetry with `recordCapture`
|
|
32012
|
+
// read as the same argument: there, findings are omitted BECAUSE the plane
|
|
32013
|
+
// re-derives them from `Event.content`; here there is no content to re-derive
|
|
32014
|
+
// from, so what is omitted is simply not sent.
|
|
32015
|
+
//
|
|
32016
|
+
// That is the wire contract as it stands rather than an oversight to patch
|
|
32017
|
+
// here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
|
|
32018
|
+
// is documented as tool-call findings — widening it to carry config-scan
|
|
32019
|
+
// findings is an egress change (a posture finding's `maskedMatch` holds the
|
|
32020
|
+
// matched command) and a decision about what an attached deployment is
|
|
32021
|
+
// entitled to, not a bug fix. An attached machine's config posture therefore
|
|
32022
|
+
// reaches the plane as the event only; the dashboard's own view of it is the
|
|
32023
|
+
// local store.
|
|
32024
|
+
async recordConfigScan(record2) {
|
|
32025
|
+
await this.deps.local.recordConfigScan(record2);
|
|
32026
|
+
await this.deps.forward.run(
|
|
32027
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
32028
|
+
);
|
|
32029
|
+
}
|
|
32030
|
+
async recordBlockedDetection(entry) {
|
|
32031
|
+
return this.deps.local.recordBlockedDetection(entry);
|
|
32032
|
+
}
|
|
32033
|
+
/**
|
|
32034
|
+
* LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
|
|
32035
|
+
* with no egress ingest endpoint, so there is nothing to forward to; adding a
|
|
32036
|
+
* forward here would be inventing a wire contract that does not exist. The
|
|
32037
|
+
* local write is the whole operation, and its summary is the real one — the
|
|
32038
|
+
* scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
|
|
32039
|
+
* returning the inner gateway's result keeps the retry semantics honest.
|
|
32040
|
+
*/
|
|
32041
|
+
async recordProjectEgress(input) {
|
|
32042
|
+
return this.deps.local.recordProjectEgress(input);
|
|
32043
|
+
}
|
|
32044
|
+
// ---------------------------------------------------------------------
|
|
32045
|
+
// Reads and device-local ledgers: pure delegation.
|
|
32046
|
+
// ---------------------------------------------------------------------
|
|
32047
|
+
async configInventoryReport() {
|
|
32048
|
+
return this.deps.local.configInventoryReport();
|
|
32049
|
+
}
|
|
32050
|
+
async readSessionProvider(sessionId) {
|
|
32051
|
+
return this.deps.local.readSessionProvider(sessionId);
|
|
32052
|
+
}
|
|
32053
|
+
async facets() {
|
|
32054
|
+
return this.deps.local.facets();
|
|
32055
|
+
}
|
|
32056
|
+
/**
|
|
32057
|
+
* Delegated UNMODIFIED — including its refusals.
|
|
32058
|
+
*
|
|
32059
|
+
* This is a fail-secure boundary: it decides whether an approved exception
|
|
32060
|
+
* lets a blocked action through. Under local-first the local store owns the
|
|
32061
|
+
* exception ledger, so the honest answer is whatever it says; wrapping this
|
|
32062
|
+
* in a fallback (`catch { return true }`, or defaulting on a timeout) would
|
|
32063
|
+
* turn a store error into a granted bypass. If the inner gateway rejects,
|
|
32064
|
+
* this rejects, and the runtime's own handling decides — which is asserted
|
|
32065
|
+
* end-to-end through runtime.capture rather than here.
|
|
32066
|
+
*/
|
|
32067
|
+
async consumeException(id) {
|
|
32068
|
+
return this.deps.local.consumeException(id);
|
|
32069
|
+
}
|
|
32070
|
+
async recentFindings(opts) {
|
|
32071
|
+
return this.deps.local.recentFindings(opts);
|
|
32072
|
+
}
|
|
32073
|
+
async healthSummary() {
|
|
32074
|
+
return this.deps.local.healthSummary();
|
|
32075
|
+
}
|
|
32076
|
+
async activityByDay(days) {
|
|
32077
|
+
return this.deps.local.activityByDay(days);
|
|
32078
|
+
}
|
|
32079
|
+
async tokenReports() {
|
|
32080
|
+
return this.deps.local.tokenReports();
|
|
32081
|
+
}
|
|
32082
|
+
async knownContentHashes() {
|
|
32083
|
+
return this.deps.local.knownContentHashes();
|
|
32084
|
+
}
|
|
32085
|
+
async scanLedger(rulesetHash) {
|
|
32086
|
+
return this.deps.local.scanLedger(rulesetHash);
|
|
32087
|
+
}
|
|
32088
|
+
async recordScanned(entries) {
|
|
32089
|
+
return this.deps.local.recordScanned(entries);
|
|
32090
|
+
}
|
|
32091
|
+
async getRuleProbeVerdict(ruleKey) {
|
|
32092
|
+
return this.deps.local.getRuleProbeVerdict(ruleKey);
|
|
32093
|
+
}
|
|
32094
|
+
async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2) {
|
|
32095
|
+
return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2);
|
|
32096
|
+
}
|
|
32097
|
+
async openAtRestKeysForPath(path) {
|
|
32098
|
+
return this.deps.local.openAtRestKeysForPath(path);
|
|
32099
|
+
}
|
|
32100
|
+
async resolvedAtRestKeysForPath(path) {
|
|
32101
|
+
return this.deps.local.resolvedAtRestKeysForPath(path);
|
|
32102
|
+
}
|
|
32103
|
+
async insertResolution(input) {
|
|
32104
|
+
return this.deps.local.insertResolution(input);
|
|
32105
|
+
}
|
|
32106
|
+
async close() {
|
|
32107
|
+
return this.deps.local.close();
|
|
32108
|
+
}
|
|
32109
|
+
// ---------------------------------------------------------------------
|
|
32110
|
+
// Policy
|
|
32111
|
+
// ---------------------------------------------------------------------
|
|
32112
|
+
async getPolicyBundle() {
|
|
32113
|
+
const local = await this.deps.local.getPolicyBundle();
|
|
32114
|
+
const cached2 = await (async () => {
|
|
32115
|
+
try {
|
|
32116
|
+
return await this.deps.readCachedBundle();
|
|
32117
|
+
} catch {
|
|
32118
|
+
return null;
|
|
32119
|
+
}
|
|
32120
|
+
})();
|
|
32121
|
+
if (cached2 === null) return local;
|
|
32122
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
32123
|
+
for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
|
|
32124
|
+
if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
|
|
32125
|
+
}
|
|
32126
|
+
const rules = [...byRuleId.values()];
|
|
32127
|
+
return {
|
|
32128
|
+
...local,
|
|
32129
|
+
// The remote version identifies the composed bundle for the poller.
|
|
32130
|
+
version: cached2.version,
|
|
32131
|
+
rules,
|
|
32132
|
+
policies: mergeRaiseOnly(
|
|
32133
|
+
local.policies,
|
|
32134
|
+
cached2.policies,
|
|
32135
|
+
ruleCategoryMap(cached2.rules, local.rules)
|
|
32136
|
+
),
|
|
32137
|
+
customKeywords: [...local.customKeywords, ...cached2.customKeywords]
|
|
32138
|
+
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
32139
|
+
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
32140
|
+
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
32141
|
+
// anything able to write policy-cache.json, a kill-switch over the
|
|
32142
|
+
// compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
|
|
32143
|
+
// zero local detection. Spread from `local` above, and deliberately not
|
|
32144
|
+
// re-read from `cached` here.
|
|
32145
|
+
//
|
|
32146
|
+
// THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
|
|
32147
|
+
// and each named here so a reader can tell a decision from an omission:
|
|
32148
|
+
//
|
|
32149
|
+
// `exceptions` — an exception SUPPRESSES a detection, so honoring
|
|
32150
|
+
// one from an unsigned on-disk cache would let
|
|
32151
|
+
// anything able to write that file turn rules off.
|
|
32152
|
+
// Every other field this merge accepts can only
|
|
32153
|
+
// RAISE enforcement; this is the one that cannot,
|
|
32154
|
+
// so it stays local-only until the bundle is
|
|
32155
|
+
// signed. Exceptions remain a device-local ledger.
|
|
32156
|
+
// `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
|
|
32157
|
+
// recoverable, which is a CUSTODY change: it puts
|
|
32158
|
+
// the detected value in the local vault instead of
|
|
32159
|
+
// destroying it. Taking that instruction from the
|
|
32160
|
+
// cache would let a remote party turn one-way
|
|
32161
|
+
// redaction into retention. Dropping it keeps the
|
|
32162
|
+
// one-way behaviour, which the schema itself calls
|
|
32163
|
+
// "the safe direction to default".
|
|
32164
|
+
// `ruleVersions` — remote rules fall back to their own spec version.
|
|
32165
|
+
// Cosmetic rather than protective: it only affects
|
|
32166
|
+
// how a finding is version-attributed, and the two
|
|
32167
|
+
// sides may therefore attribute org rules
|
|
32168
|
+
// differently. Worth carrying once there is a
|
|
32169
|
+
// reader that needs it; nothing reads it today.
|
|
32170
|
+
};
|
|
32171
|
+
}
|
|
32172
|
+
// ---------------------------------------------------------------------
|
|
32173
|
+
// LocalStoreMaintenance — by delegation (D3).
|
|
32174
|
+
//
|
|
32175
|
+
// Implementing these is what actually closes the skipped-local-maintenance
|
|
32176
|
+
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
32177
|
+
// any object carrying all five, so the composite qualifies and SessionStart
|
|
32178
|
+
// runs maintenance on the device's real store.
|
|
32179
|
+
//
|
|
32180
|
+
// ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
32181
|
+
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
32182
|
+
// return value directly; declaring them `async` here would hand those call
|
|
32183
|
+
// sites a Promise and silently break both.
|
|
32184
|
+
// ---------------------------------------------------------------------
|
|
32185
|
+
async sweepTerminalExceptions(retentionMs) {
|
|
32186
|
+
return this.deps.local.sweepTerminalExceptions(retentionMs);
|
|
32187
|
+
}
|
|
32188
|
+
capWarnEraEnforcement(policyMode) {
|
|
32189
|
+
return this.deps.local.capWarnEraEnforcement(policyMode);
|
|
32190
|
+
}
|
|
32191
|
+
async recordProjectFiles(projectId, scan2) {
|
|
32192
|
+
return this.deps.local.recordProjectFiles(projectId, scan2);
|
|
32193
|
+
}
|
|
32194
|
+
async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
|
|
32195
|
+
return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
|
|
32196
|
+
}
|
|
32197
|
+
staleBinaryNotice(currentVersion) {
|
|
32198
|
+
return this.deps.local.staleBinaryNotice(currentVersion);
|
|
32199
|
+
}
|
|
32200
|
+
};
|
|
32201
|
+
function reKeyForForward(event, remote) {
|
|
32202
|
+
if (remote === null) {
|
|
32203
|
+
const stripped = { ...event };
|
|
32204
|
+
delete stripped.hostId;
|
|
32205
|
+
delete stripped.harnessId;
|
|
32206
|
+
delete stripped.sourceProjectId;
|
|
32207
|
+
return stripped;
|
|
32208
|
+
}
|
|
32209
|
+
const rekeyed = { ...event };
|
|
32210
|
+
delete rekeyed.hostId;
|
|
32211
|
+
delete rekeyed.harnessId;
|
|
32212
|
+
delete rekeyed.sourceProjectId;
|
|
32213
|
+
if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
|
|
32214
|
+
if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
|
|
32215
|
+
if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
|
|
32216
|
+
return rekeyed;
|
|
32217
|
+
}
|
|
32218
|
+
var BATCH_FORWARD_BUDGET_MS = 3e3;
|
|
32219
|
+
function llmAuditEvent(input) {
|
|
32220
|
+
return {
|
|
32221
|
+
id: llmCallId(input.sessionId, input.messageId),
|
|
32222
|
+
eventType: "llm_call",
|
|
32223
|
+
startedAt: input.startedAt,
|
|
32224
|
+
parentId: input.parentId,
|
|
32225
|
+
rootSessionId: input.rootSessionId,
|
|
32226
|
+
attributes: input.attributes
|
|
32227
|
+
};
|
|
32228
|
+
}
|
|
32229
|
+
function toolAuditEvent(input) {
|
|
32230
|
+
return {
|
|
32231
|
+
id: toolCallId(input.sessionId, input.toolUseId),
|
|
32232
|
+
eventType: "tool_call",
|
|
32233
|
+
startedAt: input.startedAt,
|
|
32234
|
+
parentId: input.parentId,
|
|
32235
|
+
rootSessionId: input.rootSessionId,
|
|
32236
|
+
attributes: input.attributes,
|
|
32237
|
+
inspections: input.inspections
|
|
32238
|
+
};
|
|
32239
|
+
}
|
|
32240
|
+
|
|
32241
|
+
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
32242
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
32243
|
+
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
32244
|
+
import { join as join21 } from "path";
|
|
32245
|
+
|
|
32246
|
+
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
32247
|
+
import { rename as rename2 } from "fs/promises";
|
|
32248
|
+
var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
32249
|
+
var ATTEMPTS = 5;
|
|
32250
|
+
var delay = (ms) => new Promise((resolve3) => {
|
|
32251
|
+
setTimeout(resolve3, ms);
|
|
32252
|
+
});
|
|
32253
|
+
async function publishByRename(tmp, file2, move = rename2) {
|
|
32254
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
32255
|
+
try {
|
|
32256
|
+
await move(tmp, file2);
|
|
32257
|
+
return;
|
|
32258
|
+
} catch (err) {
|
|
32259
|
+
const code = err.code;
|
|
32260
|
+
if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
|
|
32261
|
+
await delay(attempt * 10);
|
|
32262
|
+
}
|
|
32263
|
+
}
|
|
32264
|
+
}
|
|
32265
|
+
|
|
32266
|
+
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
32267
|
+
function createPolicyStore(dir = dataDir()) {
|
|
32268
|
+
const file2 = join21(dir, "policy-cache.json");
|
|
32269
|
+
async function read() {
|
|
32270
|
+
try {
|
|
32271
|
+
const raw = await readFile2(file2, "utf8");
|
|
32272
|
+
const parsed2 = JSON.parse(raw);
|
|
32273
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
32274
|
+
const record2 = parsed2;
|
|
32275
|
+
const bundle = PolicyBundle.parse(record2.bundle);
|
|
32276
|
+
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
32277
|
+
const etag = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
32278
|
+
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
32279
|
+
} catch {
|
|
32280
|
+
return null;
|
|
32281
|
+
}
|
|
32282
|
+
}
|
|
32283
|
+
async function write(bundle, etag) {
|
|
32284
|
+
await ensureDataDir(dir);
|
|
32285
|
+
const stored = {
|
|
32286
|
+
bundle,
|
|
32287
|
+
fetchedAtMs: Date.now(),
|
|
32288
|
+
...etag === void 0 ? {} : { etag }
|
|
32289
|
+
};
|
|
32290
|
+
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
32291
|
+
try {
|
|
32292
|
+
await writeFile2(tmp, JSON.stringify(stored), {
|
|
32293
|
+
encoding: "utf8",
|
|
32294
|
+
mode: DATA_FILE_MODE,
|
|
32295
|
+
flag: "wx"
|
|
32296
|
+
});
|
|
32297
|
+
await publishByRename(tmp, file2);
|
|
32298
|
+
} catch (err) {
|
|
32299
|
+
await rm(tmp, { force: true }).catch(() => void 0);
|
|
32300
|
+
throw err;
|
|
32301
|
+
}
|
|
32302
|
+
}
|
|
32303
|
+
return { read, write, file: file2 };
|
|
32304
|
+
}
|
|
32305
|
+
|
|
32306
|
+
// ../../packages/remote/src/http.ts
|
|
32307
|
+
import { request as httpRequest } from "http";
|
|
32308
|
+
import { request as httpsRequest } from "https";
|
|
32309
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
32310
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
32311
|
+
var RemoteRequestError = class extends Error {
|
|
32312
|
+
constructor(status) {
|
|
32313
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
32314
|
+
this.status = status;
|
|
32315
|
+
this.name = "RemoteRequestError";
|
|
32316
|
+
}
|
|
32317
|
+
status;
|
|
32318
|
+
};
|
|
32319
|
+
var RemoteRequestInvalid = class extends Error {
|
|
32320
|
+
constructor(route, cause) {
|
|
32321
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
32322
|
+
this.cause = cause;
|
|
32323
|
+
this.name = "RemoteRequestInvalid";
|
|
32324
|
+
}
|
|
32325
|
+
cause;
|
|
32326
|
+
};
|
|
32327
|
+
var RemoteResponseInvalid = class extends Error {
|
|
32328
|
+
constructor(route, detail) {
|
|
32329
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
32330
|
+
this.name = "RemoteResponseInvalid";
|
|
32331
|
+
}
|
|
32332
|
+
};
|
|
32333
|
+
var RemoteTransportError = class extends Error {
|
|
32334
|
+
/**
|
|
32335
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
32336
|
+
* refused.
|
|
32337
|
+
*
|
|
32338
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
32339
|
+
* all. It exists because two paths reject after a status has already been
|
|
32340
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
32341
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
32342
|
+
* outage, which sends the reader to look at their network instead of their
|
|
32343
|
+
* credential.
|
|
32344
|
+
*/
|
|
32345
|
+
constructor(reason, status) {
|
|
32346
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
32347
|
+
this.status = status;
|
|
32348
|
+
this.name = "RemoteTransportError";
|
|
32349
|
+
}
|
|
32350
|
+
status;
|
|
32351
|
+
};
|
|
32352
|
+
async function send(options) {
|
|
32353
|
+
const url2 = new URL(options.url);
|
|
32354
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32355
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
32356
|
+
const requestOptions = {
|
|
32357
|
+
method: options.method,
|
|
32358
|
+
headers: {
|
|
32359
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
32360
|
+
// last they win, and two of the values below are ones no caller may
|
|
32361
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
32362
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
32363
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
32364
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
32365
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
32366
|
+
// conditional GET — is untouched by this order.
|
|
32367
|
+
...options.headers,
|
|
32368
|
+
// The credential. One header, matching what the deployment authenticates
|
|
32369
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
32370
|
+
// it can be logged by an intermediary for no gain.
|
|
32371
|
+
"x-api-key": options.apiKey,
|
|
32372
|
+
accept: "application/json",
|
|
32373
|
+
...options.body === void 0 ? {} : {
|
|
32374
|
+
"content-type": "application/json",
|
|
32375
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
32376
|
+
// character count is truncated by the receiver.
|
|
32377
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
32378
|
+
}
|
|
32379
|
+
}
|
|
32380
|
+
};
|
|
32381
|
+
return new Promise((resolve3, reject) => {
|
|
32382
|
+
let settled = false;
|
|
32383
|
+
const fail = (reason, status) => {
|
|
32384
|
+
if (settled) return;
|
|
32385
|
+
settled = true;
|
|
32386
|
+
reject(new RemoteTransportError(reason, status));
|
|
32387
|
+
};
|
|
32388
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
32389
|
+
const chunks = [];
|
|
32390
|
+
let size = 0;
|
|
32391
|
+
res.on("data", (chunk) => {
|
|
32392
|
+
size += chunk.length;
|
|
32393
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
32394
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
32395
|
+
res.destroy();
|
|
32396
|
+
req.destroy();
|
|
32397
|
+
return;
|
|
32398
|
+
}
|
|
32399
|
+
chunks.push(chunk);
|
|
32400
|
+
});
|
|
32401
|
+
res.on("aborted", () => {
|
|
32402
|
+
fail("the response was aborted", res.statusCode);
|
|
32403
|
+
});
|
|
32404
|
+
res.on("end", () => {
|
|
32405
|
+
if (settled) return;
|
|
32406
|
+
settled = true;
|
|
32407
|
+
resolve3({
|
|
32408
|
+
status: res.statusCode ?? 0,
|
|
32409
|
+
headers: res.headers,
|
|
32410
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
32411
|
+
});
|
|
32412
|
+
});
|
|
32413
|
+
});
|
|
32414
|
+
const deadline = setTimeout(() => {
|
|
32415
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
32416
|
+
req.destroy();
|
|
32417
|
+
}, timeoutMs);
|
|
32418
|
+
deadline.unref();
|
|
32419
|
+
req.on("upgrade", (_res, socket) => {
|
|
32420
|
+
fail("the deployment answered with a protocol upgrade");
|
|
32421
|
+
socket.destroy();
|
|
32422
|
+
});
|
|
32423
|
+
req.on("close", () => {
|
|
32424
|
+
fail("the connection closed before a response was read");
|
|
32425
|
+
clearTimeout(deadline);
|
|
32426
|
+
});
|
|
32427
|
+
req.on("error", (err) => {
|
|
32428
|
+
fail(err.message);
|
|
32429
|
+
});
|
|
32430
|
+
if (options.body !== void 0) req.write(options.body);
|
|
32431
|
+
req.end();
|
|
32432
|
+
});
|
|
32433
|
+
}
|
|
32434
|
+
|
|
32435
|
+
// ../../packages/remote/src/client.ts
|
|
32436
|
+
var ROUTES = {
|
|
32437
|
+
events: "/v1/events",
|
|
32438
|
+
auditEvents: "/v1/audit-events",
|
|
32439
|
+
inventory: "/v1/inventory",
|
|
32440
|
+
storePosture: "/v1/store-posture",
|
|
32441
|
+
policyBundle: "/v1/policy-bundle",
|
|
32442
|
+
whoami: "/v1/plugin/whoami"
|
|
32443
|
+
};
|
|
32444
|
+
function headerValue(response, name) {
|
|
32445
|
+
const raw = response.headers[name];
|
|
32446
|
+
if (raw === void 0) return void 0;
|
|
32447
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
32448
|
+
}
|
|
32449
|
+
function okBody(response) {
|
|
32450
|
+
if (response.status < 200 || response.status >= 300) {
|
|
32451
|
+
throw new RemoteRequestError(response.status);
|
|
32452
|
+
}
|
|
32453
|
+
return response.body;
|
|
32454
|
+
}
|
|
32455
|
+
function parsed(schema, body, route) {
|
|
32456
|
+
let json2;
|
|
32457
|
+
try {
|
|
32458
|
+
json2 = JSON.parse(body);
|
|
32459
|
+
} catch {
|
|
32460
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
32461
|
+
}
|
|
32462
|
+
const result = schema.safeParse(json2);
|
|
32463
|
+
if (!result.success) {
|
|
32464
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
32465
|
+
}
|
|
32466
|
+
return result.data;
|
|
32467
|
+
}
|
|
32468
|
+
function withoutTrailingSlashes(endpoint) {
|
|
32469
|
+
let end = endpoint.length;
|
|
32470
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
32471
|
+
return endpoint.slice(0, end);
|
|
32472
|
+
}
|
|
32473
|
+
var SLASH = "/".charCodeAt(0);
|
|
32474
|
+
function createRemoteClient(options) {
|
|
32475
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
32476
|
+
const url2 = (route) => `${base}${route}`;
|
|
32477
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
32478
|
+
return {
|
|
32479
|
+
async ingestEvents(batch) {
|
|
32480
|
+
const response = await send({
|
|
32481
|
+
...common,
|
|
32482
|
+
method: "POST",
|
|
32483
|
+
url: url2(ROUTES.events),
|
|
32484
|
+
body: JSON.stringify(batch)
|
|
32485
|
+
});
|
|
32486
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
32487
|
+
},
|
|
32488
|
+
async ingestInventory(context) {
|
|
32489
|
+
const response = await send({
|
|
32490
|
+
...common,
|
|
32491
|
+
method: "POST",
|
|
32492
|
+
url: url2(ROUTES.inventory),
|
|
32493
|
+
body: JSON.stringify(context)
|
|
32494
|
+
});
|
|
32495
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
32496
|
+
},
|
|
32497
|
+
async recordAuditEvent(event) {
|
|
32498
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
32499
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
32500
|
+
const submission = validated.data;
|
|
32501
|
+
const response = await send({
|
|
32502
|
+
...common,
|
|
32503
|
+
method: "POST",
|
|
32504
|
+
url: url2(ROUTES.auditEvents),
|
|
32505
|
+
body: JSON.stringify(submission)
|
|
32506
|
+
});
|
|
32507
|
+
okBody(response);
|
|
32508
|
+
},
|
|
32509
|
+
async reportStorePosture(snapshot) {
|
|
32510
|
+
const response = await send({
|
|
32511
|
+
...common,
|
|
32512
|
+
method: "POST",
|
|
32513
|
+
url: url2(ROUTES.storePosture),
|
|
32514
|
+
body: JSON.stringify(snapshot)
|
|
32515
|
+
});
|
|
32516
|
+
okBody(response);
|
|
32517
|
+
},
|
|
32518
|
+
async getPolicyBundle(etag) {
|
|
32519
|
+
const response = await send({
|
|
32520
|
+
...common,
|
|
32521
|
+
method: "GET",
|
|
32522
|
+
url: url2(ROUTES.policyBundle),
|
|
32523
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
32524
|
+
});
|
|
32525
|
+
if (response.status === 304) {
|
|
32526
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
32527
|
+
}
|
|
32528
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
32529
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
32530
|
+
},
|
|
32531
|
+
async whoami() {
|
|
32532
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
32533
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
32534
|
+
}
|
|
32535
|
+
};
|
|
32536
|
+
}
|
|
32537
|
+
|
|
32538
|
+
// ../../packages/plugin-runtime/src/attached/posture-reporter.ts
|
|
32539
|
+
var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
32540
|
+
function createPostureReporter(deps) {
|
|
32541
|
+
async function prepare() {
|
|
32542
|
+
try {
|
|
32543
|
+
const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
|
|
32544
|
+
if (state === null) return null;
|
|
32545
|
+
const nowMs = deps.now();
|
|
32546
|
+
const elapsed = nowMs - state.lastAttemptedAtMs;
|
|
32547
|
+
if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
|
|
32548
|
+
try {
|
|
32549
|
+
await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
|
|
32550
|
+
} catch {
|
|
32551
|
+
}
|
|
32552
|
+
const { readError, ...measurement } = deps.readStore();
|
|
32553
|
+
if (readError) return null;
|
|
32554
|
+
let plugin;
|
|
32555
|
+
try {
|
|
32556
|
+
plugin = await deps.pluginBlock?.();
|
|
32557
|
+
} catch {
|
|
32558
|
+
plugin = void 0;
|
|
32559
|
+
}
|
|
32560
|
+
return {
|
|
32561
|
+
deviceId: state.deviceId,
|
|
32562
|
+
hostname: deps.hostname(),
|
|
32563
|
+
capturedAt: nowMs,
|
|
32564
|
+
...measurement,
|
|
32565
|
+
// Omit the key rather than spread an explicit `undefined` —
|
|
32566
|
+
// exactOptionalPropertyTypes distinguishes the two, and the bridge in
|
|
32567
|
+
// factory.ts keys on presence.
|
|
32568
|
+
...plugin === void 0 ? {} : { plugin }
|
|
32569
|
+
};
|
|
32570
|
+
} catch {
|
|
32571
|
+
return null;
|
|
32572
|
+
}
|
|
32573
|
+
}
|
|
32574
|
+
async function send2(snapshot) {
|
|
32575
|
+
try {
|
|
32576
|
+
await deps.report(snapshot);
|
|
32577
|
+
} catch {
|
|
32578
|
+
}
|
|
32579
|
+
}
|
|
32580
|
+
return { prepare, send: send2 };
|
|
32581
|
+
}
|
|
32582
|
+
|
|
32583
|
+
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
32584
|
+
import { statSync as statSync9 } from "fs";
|
|
32585
|
+
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
32586
|
+
|
|
32587
|
+
// ../../packages/plugin-runtime/src/attached/action-counts.ts
|
|
32588
|
+
function emptyActionCounts() {
|
|
32589
|
+
return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
|
|
32590
|
+
}
|
|
32591
|
+
function isActionTaken(value) {
|
|
32592
|
+
return ACTION_TAKEN_KEYS.includes(value);
|
|
32593
|
+
}
|
|
32594
|
+
|
|
32595
|
+
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
32596
|
+
var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
|
|
32597
|
+
function isSchemaAbsent(err) {
|
|
32598
|
+
return err instanceof Error && /no such table/i.test(err.message);
|
|
32599
|
+
}
|
|
32600
|
+
function emptyReadout(readError = false) {
|
|
32601
|
+
const byAction = emptyActionCounts();
|
|
32602
|
+
return {
|
|
32603
|
+
storePresent: false,
|
|
32604
|
+
schemaVersion: null,
|
|
32605
|
+
findingsTotal: 0,
|
|
32606
|
+
findingsFirstAt: null,
|
|
32607
|
+
findingsLastAt: null,
|
|
32608
|
+
packs: [],
|
|
32609
|
+
policyCounts: { total: 0, disabled: 0, byAction },
|
|
32610
|
+
readError
|
|
32611
|
+
};
|
|
32612
|
+
}
|
|
32613
|
+
function readStorePosture(dbPath2) {
|
|
32614
|
+
try {
|
|
32615
|
+
statSync9(dbPath2);
|
|
32616
|
+
} catch (err) {
|
|
32617
|
+
const code = err.code;
|
|
32618
|
+
if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
|
|
32619
|
+
return emptyReadout(true);
|
|
32620
|
+
}
|
|
32621
|
+
let db = null;
|
|
32622
|
+
let version2 = null;
|
|
32623
|
+
let packs2 = [];
|
|
32624
|
+
let policyCounts = {
|
|
32625
|
+
total: 0,
|
|
32626
|
+
disabled: 0,
|
|
32627
|
+
byAction: emptyActionCounts()
|
|
32628
|
+
};
|
|
32629
|
+
let findingsTotal = 0;
|
|
32630
|
+
let findingsFirstAt = null;
|
|
32631
|
+
let findingsLastAt = null;
|
|
32632
|
+
const currentReadout = () => ({
|
|
32633
|
+
storePresent: true,
|
|
32634
|
+
schemaVersion: version2,
|
|
32635
|
+
findingsTotal,
|
|
32636
|
+
findingsFirstAt,
|
|
32637
|
+
findingsLastAt,
|
|
32638
|
+
packs: packs2,
|
|
32639
|
+
policyCounts,
|
|
32640
|
+
readError: false
|
|
32641
|
+
});
|
|
32642
|
+
try {
|
|
32643
|
+
db = new DatabaseSync3(dbPath2, { readOnly: true });
|
|
32644
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
32645
|
+
version2 = db.prepare("PRAGMA user_version").get().user_version;
|
|
32646
|
+
try {
|
|
32647
|
+
const packRows = db.prepare(
|
|
32648
|
+
`SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
|
|
32649
|
+
).all();
|
|
32650
|
+
packs2 = packRows.map((r) => ({
|
|
32651
|
+
packId: `${r.namespace}/${r.pack_id}`,
|
|
32652
|
+
version: r.version,
|
|
32653
|
+
enabled: r.enabled !== 0,
|
|
32654
|
+
updatedAt: r.updated_at == null ? null : String(r.updated_at)
|
|
32655
|
+
}));
|
|
32656
|
+
} catch (err) {
|
|
32657
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32658
|
+
}
|
|
32659
|
+
try {
|
|
32660
|
+
const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
|
|
32661
|
+
const byAction = emptyActionCounts();
|
|
32662
|
+
let disabled = 0;
|
|
32663
|
+
for (const row of policyRows) {
|
|
32664
|
+
if (row.enabled === 0) disabled += 1;
|
|
32665
|
+
if (isActionTaken(row.action)) byAction[row.action] += 1;
|
|
32666
|
+
}
|
|
32667
|
+
policyCounts = { total: policyRows.length, disabled, byAction };
|
|
32668
|
+
} catch (err) {
|
|
32669
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32670
|
+
}
|
|
32671
|
+
try {
|
|
32672
|
+
const agg = db.prepare(
|
|
32673
|
+
`SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
|
|
32674
|
+
FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
|
|
32675
|
+
WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
|
|
32676
|
+
).get();
|
|
32677
|
+
findingsTotal = agg.n;
|
|
32678
|
+
findingsFirstAt = agg.firstAt;
|
|
32679
|
+
findingsLastAt = agg.lastAt;
|
|
32680
|
+
} catch (err) {
|
|
32681
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32682
|
+
}
|
|
32683
|
+
return currentReadout();
|
|
32684
|
+
} catch {
|
|
32685
|
+
return emptyReadout(true);
|
|
32686
|
+
} finally {
|
|
32687
|
+
try {
|
|
32688
|
+
db?.close();
|
|
32689
|
+
} catch {
|
|
32690
|
+
}
|
|
32691
|
+
}
|
|
32692
|
+
}
|
|
32693
|
+
|
|
32694
|
+
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
32695
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
32696
|
+
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
32697
|
+
import { join as join22 } from "path";
|
|
32698
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
32699
|
+
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
32700
|
+
const file2 = join22(dir, "posture-state.json");
|
|
32701
|
+
const legacyFile = legacyDir === void 0 ? null : join22(legacyDir, "posture-state.json");
|
|
32702
|
+
async function persist(state) {
|
|
32703
|
+
await ensureDataDir(dir);
|
|
32704
|
+
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
32705
|
+
try {
|
|
32706
|
+
await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
32707
|
+
await publishByRename(tmp, file2);
|
|
32708
|
+
} catch (err) {
|
|
32709
|
+
await rm2(tmp, { force: true }).catch(() => void 0);
|
|
32710
|
+
throw err;
|
|
32711
|
+
}
|
|
32712
|
+
}
|
|
32713
|
+
async function readFrom(path) {
|
|
32714
|
+
let raw;
|
|
32715
|
+
try {
|
|
32716
|
+
raw = await readFile3(path, "utf8");
|
|
32717
|
+
} catch (err) {
|
|
32718
|
+
const code = err.code;
|
|
32719
|
+
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
32720
|
+
throw err;
|
|
32721
|
+
}
|
|
32722
|
+
try {
|
|
32723
|
+
const parsed2 = JSON.parse(raw);
|
|
32724
|
+
if (typeof parsed2 === "object" && parsed2 !== null) {
|
|
32725
|
+
const record2 = parsed2;
|
|
32726
|
+
if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
|
|
32727
|
+
const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
|
|
32728
|
+
return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
|
|
32729
|
+
}
|
|
32730
|
+
}
|
|
32731
|
+
} catch {
|
|
32732
|
+
}
|
|
32733
|
+
return null;
|
|
32734
|
+
}
|
|
32735
|
+
async function read() {
|
|
32736
|
+
const current = await readFrom(file2);
|
|
32737
|
+
if (current) return current;
|
|
32738
|
+
const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
|
|
32739
|
+
if (legacy) {
|
|
32740
|
+
try {
|
|
32741
|
+
await persist(legacy);
|
|
32742
|
+
} catch {
|
|
32743
|
+
}
|
|
32744
|
+
return legacy;
|
|
32745
|
+
}
|
|
32746
|
+
const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
|
|
32747
|
+
try {
|
|
32748
|
+
await ensureDataDir(dir);
|
|
32749
|
+
if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
|
|
32750
|
+
} catch {
|
|
32751
|
+
return null;
|
|
32752
|
+
}
|
|
32753
|
+
const winner = await readFrom(file2).catch(() => null);
|
|
32754
|
+
if (winner) return winner;
|
|
32755
|
+
try {
|
|
32756
|
+
await persist(fresh);
|
|
32757
|
+
} catch {
|
|
32758
|
+
return null;
|
|
32759
|
+
}
|
|
32760
|
+
return fresh;
|
|
32761
|
+
}
|
|
32762
|
+
async function markAttempted(deviceId, atMs) {
|
|
32763
|
+
await persist({ deviceId, lastAttemptedAtMs: atMs });
|
|
32764
|
+
}
|
|
32765
|
+
return { read, markAttempted, file: file2 };
|
|
32766
|
+
}
|
|
32767
|
+
|
|
32768
|
+
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
32769
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
32770
|
+
import { join as join23 } from "path";
|
|
32771
|
+
|
|
32772
|
+
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
32773
|
+
var REFUSAL_LINES = {
|
|
32774
|
+
unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
|
|
32775
|
+
forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
|
|
32776
|
+
};
|
|
32777
|
+
var OUTCOME_LINES = {
|
|
32778
|
+
ok: "policy synced",
|
|
32779
|
+
"not-modified": "policy up to date",
|
|
32780
|
+
unauthorized: REFUSAL_LINES.unauthorized,
|
|
32781
|
+
forbidden: REFUSAL_LINES.forbidden,
|
|
32782
|
+
unreachable: "control plane unreachable at last attempt",
|
|
32783
|
+
"invalid-bundle": "control plane sent a policy bundle this build cannot read"
|
|
32784
|
+
};
|
|
32785
|
+
|
|
32786
|
+
// ../../packages/plugin-runtime/src/attached/sync-trigger.ts
|
|
32787
|
+
import { spawn } from "child_process";
|
|
32788
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
32789
|
+
var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
|
|
32790
|
+
|
|
32791
|
+
// ../../packages/plugin-runtime/src/attached/factory.ts
|
|
32792
|
+
import { hostname as hostname5 } from "os";
|
|
32793
|
+
|
|
32794
|
+
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
32795
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
31381
32796
|
|
|
31382
32797
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
31383
32798
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -31418,12 +32833,12 @@ var StandaloneDataGateway = class {
|
|
|
31418
32833
|
// reconciler drops the whole pass and recovers it idempotently on the next read.
|
|
31419
32834
|
recordLlmCalls(inputs) {
|
|
31420
32835
|
if (inputs.length === 0) return Promise.resolve();
|
|
31421
|
-
return new Promise((
|
|
32836
|
+
return new Promise((resolve3, reject) => {
|
|
31422
32837
|
try {
|
|
31423
32838
|
this.db.auditEvents.runInTransaction(() => {
|
|
31424
32839
|
for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
|
|
31425
32840
|
});
|
|
31426
|
-
|
|
32841
|
+
resolve3();
|
|
31427
32842
|
} catch (err) {
|
|
31428
32843
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
31429
32844
|
}
|
|
@@ -31435,12 +32850,12 @@ var StandaloneDataGateway = class {
|
|
|
31435
32850
|
// drops the whole pass and recovers it idempotently next time.
|
|
31436
32851
|
recordToolCalls(inputs) {
|
|
31437
32852
|
if (inputs.length === 0) return Promise.resolve();
|
|
31438
|
-
return new Promise((
|
|
32853
|
+
return new Promise((resolve3, reject) => {
|
|
31439
32854
|
try {
|
|
31440
32855
|
this.db.auditEvents.runInTransaction(() => {
|
|
31441
32856
|
for (const input of inputs) this.writeToolCall(input);
|
|
31442
32857
|
});
|
|
31443
|
-
|
|
32858
|
+
resolve3();
|
|
31444
32859
|
} catch (err) {
|
|
31445
32860
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
31446
32861
|
}
|
|
@@ -31582,7 +32997,7 @@ var StandaloneDataGateway = class {
|
|
|
31582
32997
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
31583
32998
|
const installed = this.installedScanRules();
|
|
31584
32999
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
31585
|
-
id:
|
|
33000
|
+
id: randomUUID18(),
|
|
31586
33001
|
scope: "global",
|
|
31587
33002
|
target: { ruleId },
|
|
31588
33003
|
action,
|
|
@@ -31736,23 +33151,69 @@ var StandaloneDataGateway = class {
|
|
|
31736
33151
|
}
|
|
31737
33152
|
};
|
|
31738
33153
|
|
|
33154
|
+
// ../../packages/plugin-runtime/src/attached/factory.ts
|
|
33155
|
+
function resolveGatewayForConfig(config2, meta3) {
|
|
33156
|
+
const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
|
|
33157
|
+
try {
|
|
33158
|
+
if (!isAttached(config2.settings)) return local;
|
|
33159
|
+
const connection = config2.settings.controlPlane;
|
|
33160
|
+
if (connection === void 0) return local;
|
|
33161
|
+
const state = readControlPlaneCredentialState(config2.settingsDir, connection);
|
|
33162
|
+
if (!state.usable) return local;
|
|
33163
|
+
const client = createRemoteClient({
|
|
33164
|
+
endpoint: connection.endpoint,
|
|
33165
|
+
apiKey: state.credential.apiKey
|
|
33166
|
+
});
|
|
33167
|
+
const store = createPolicyStore(config2.dataDir);
|
|
33168
|
+
const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
|
|
33169
|
+
const forward = createForwardPolicy({ dir: config2.dataDir });
|
|
33170
|
+
return new AttachedDataGateway({
|
|
33171
|
+
local,
|
|
33172
|
+
client,
|
|
33173
|
+
dataDir: config2.dataDir,
|
|
33174
|
+
readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
|
|
33175
|
+
forward,
|
|
33176
|
+
posture: createPostureReporter({
|
|
33177
|
+
// THROUGH THE BREAKER, and wrapped HERE rather than around
|
|
33178
|
+
// `PostureReporter.send`. The reporter swallows every error by
|
|
33179
|
+
// contract, so a wrap outside it would hand `forward.run` a resolved
|
|
33180
|
+
// promise for a send that failed — recording a SUCCESS, clearing
|
|
33181
|
+
// `consecutiveFailures` and `lastFailure`, and telling `aka status` the
|
|
33182
|
+
// forward recovered when nothing did. Wrapping the raw client call puts
|
|
33183
|
+
// the breaker above the swallow, where it can see the truth.
|
|
33184
|
+
//
|
|
33185
|
+
// What it buys: once the breaker is open — the plane already confirmed
|
|
33186
|
+
// down by the gateway's own writes — this stops paying a request
|
|
33187
|
+
// timeout per throttle interval to re-learn it.
|
|
33188
|
+
report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
|
|
33189
|
+
store: postureStore,
|
|
33190
|
+
readStore: () => readStorePosture(config2.dbPath),
|
|
33191
|
+
hostname: () => hostname5(),
|
|
33192
|
+
now: () => Date.now()
|
|
33193
|
+
})
|
|
33194
|
+
});
|
|
33195
|
+
} catch {
|
|
33196
|
+
return local;
|
|
33197
|
+
}
|
|
33198
|
+
}
|
|
33199
|
+
|
|
31739
33200
|
// ../../packages/plugin-runtime/src/resolve.ts
|
|
31740
|
-
var
|
|
31741
|
-
var defaultGatewayFactory =
|
|
33201
|
+
var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
|
|
33202
|
+
var defaultGatewayFactory = configuredGatewayFactory;
|
|
31742
33203
|
function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
|
|
31743
33204
|
return gatewayFactory(config2, meta3);
|
|
31744
33205
|
}
|
|
31745
33206
|
|
|
31746
33207
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
31747
|
-
import { randomUUID as
|
|
33208
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
31748
33209
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
31749
33210
|
|
|
31750
33211
|
// src/history/transcripts.ts
|
|
31751
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
33212
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync15 } from "fs";
|
|
31752
33213
|
import { homedir as homedir3 } from "os";
|
|
31753
|
-
import { join as
|
|
33214
|
+
import { join as join24 } from "path";
|
|
31754
33215
|
function transcriptsDir(home) {
|
|
31755
|
-
return
|
|
33216
|
+
return join24(home ?? homedir3(), ".claude", "projects");
|
|
31756
33217
|
}
|
|
31757
33218
|
function isRecord(value) {
|
|
31758
33219
|
return typeof value === "object" && value !== null;
|
|
@@ -32000,7 +33461,7 @@ function* iterateFileContents(dir, excludeSessionId) {
|
|
|
32000
33461
|
return;
|
|
32001
33462
|
}
|
|
32002
33463
|
for (const project of projects) {
|
|
32003
|
-
const projectDir =
|
|
33464
|
+
const projectDir = join24(dir, project);
|
|
32004
33465
|
let files;
|
|
32005
33466
|
try {
|
|
32006
33467
|
files = readdirSync5(projectDir).filter((name) => name.endsWith(".jsonl"));
|
|
@@ -32010,10 +33471,10 @@ function* iterateFileContents(dir, excludeSessionId) {
|
|
|
32010
33471
|
for (const file2 of files) {
|
|
32011
33472
|
if (excludeSessionId !== void 0 && file2.slice(0, -".jsonl".length) === excludeSessionId)
|
|
32012
33473
|
continue;
|
|
32013
|
-
const filePath =
|
|
33474
|
+
const filePath = join24(projectDir, file2);
|
|
32014
33475
|
let content;
|
|
32015
33476
|
try {
|
|
32016
|
-
content =
|
|
33477
|
+
content = readFileSync15(filePath, "utf8");
|
|
32017
33478
|
} catch {
|
|
32018
33479
|
continue;
|
|
32019
33480
|
}
|
|
@@ -32145,17 +33606,17 @@ async function scanHistory(config2, opts = {}, onHit) {
|
|
|
32145
33606
|
}
|
|
32146
33607
|
|
|
32147
33608
|
// src/history/tail-scrub.ts
|
|
32148
|
-
import { readFileSync as
|
|
33609
|
+
import { readFileSync as readFileSync17, renameSync as renameSync6, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
32149
33610
|
|
|
32150
33611
|
// src/remediation/redact.ts
|
|
32151
|
-
import { readFileSync as
|
|
32152
|
-
import { isAbsolute as isAbsolute2, relative, resolve } from "path";
|
|
33612
|
+
import { readFileSync as readFileSync16, realpathSync as realpathSync4, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
33613
|
+
import { isAbsolute as isAbsolute2, relative, resolve as resolve2 } from "path";
|
|
32153
33614
|
function platformRedactionScope(home) {
|
|
32154
33615
|
return { artifactRoots: [transcriptsDir(home)] };
|
|
32155
33616
|
}
|
|
32156
33617
|
function realPathOrNull(path) {
|
|
32157
33618
|
try {
|
|
32158
|
-
return
|
|
33619
|
+
return realpathSync4(path);
|
|
32159
33620
|
} catch {
|
|
32160
33621
|
return null;
|
|
32161
33622
|
}
|
|
@@ -32167,7 +33628,7 @@ function isWithinRoot(realTarget, root) {
|
|
|
32167
33628
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
32168
33629
|
}
|
|
32169
33630
|
function resolveRedactableArtifact(filePath, scope) {
|
|
32170
|
-
const realTarget = realPathOrNull(
|
|
33631
|
+
const realTarget = realPathOrNull(resolve2(filePath));
|
|
32171
33632
|
if (realTarget === null) return null;
|
|
32172
33633
|
return scope.artifactRoots.some((root) => isWithinRoot(realTarget, root)) ? realTarget : null;
|
|
32173
33634
|
}
|
|
@@ -32178,9 +33639,9 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
32178
33639
|
try {
|
|
32179
33640
|
const realPath = resolveRedactableArtifact(filePath, deps.scope);
|
|
32180
33641
|
if (realPath === null) return null;
|
|
32181
|
-
const statBefore =
|
|
33642
|
+
const statBefore = statSync10(realPath);
|
|
32182
33643
|
if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
|
|
32183
|
-
const content =
|
|
33644
|
+
const content = readFileSync17(realPath, "utf8");
|
|
32184
33645
|
const lines = content.split("\n");
|
|
32185
33646
|
let rewritten = 0;
|
|
32186
33647
|
for (const [i, line] of lines.entries()) {
|
|
@@ -32195,15 +33656,15 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
32195
33656
|
const tmpPath = `${realPath}.aka-scrub.tmp`;
|
|
32196
33657
|
try {
|
|
32197
33658
|
writeFileSync10(tmpPath, lines.join("\n"), { mode: statBefore.mode & 511 });
|
|
32198
|
-
const statNow =
|
|
33659
|
+
const statNow = statSync10(realPath);
|
|
32199
33660
|
if (statNow.size !== statBefore.size || statNow.mtimeMs !== statBefore.mtimeMs) {
|
|
32200
|
-
|
|
33661
|
+
rmSync8(tmpPath, { force: true, recursive: true });
|
|
32201
33662
|
return null;
|
|
32202
33663
|
}
|
|
32203
33664
|
renameSync6(tmpPath, realPath);
|
|
32204
33665
|
} catch {
|
|
32205
33666
|
try {
|
|
32206
|
-
|
|
33667
|
+
rmSync8(tmpPath, { force: true, recursive: true });
|
|
32207
33668
|
} catch {
|
|
32208
33669
|
}
|
|
32209
33670
|
return null;
|
|
@@ -32221,11 +33682,11 @@ import {
|
|
|
32221
33682
|
fstatSync,
|
|
32222
33683
|
mkdirSync as mkdirSync4,
|
|
32223
33684
|
openSync as openSync2,
|
|
32224
|
-
readFileSync as
|
|
33685
|
+
readFileSync as readFileSync18,
|
|
32225
33686
|
readSync,
|
|
32226
33687
|
writeFileSync as writeFileSync11
|
|
32227
33688
|
} from "fs";
|
|
32228
|
-
import { join as
|
|
33689
|
+
import { join as join25 } from "path";
|
|
32229
33690
|
|
|
32230
33691
|
// src/history/usage.ts
|
|
32231
33692
|
var NO_PROJECT_CWD = "/nonexistent/aka-reconciler/no-project";
|
|
@@ -32582,7 +34043,7 @@ function buildTranscriptScrubber() {
|
|
|
32582
34043
|
scope
|
|
32583
34044
|
});
|
|
32584
34045
|
}
|
|
32585
|
-
if (process.argv[1] &&
|
|
34046
|
+
if (process.argv[1] && fileURLToPath3(import.meta.url) === process.argv[1]) {
|
|
32586
34047
|
const triage = process.argv.includes("--triage");
|
|
32587
34048
|
const startedAt = Date.now();
|
|
32588
34049
|
const sessionId = process.env.CLAUDE_CODE_BRIDGE_SESSION_ID;
|
|
@@ -32604,9 +34065,9 @@ if (process.argv[1] && fileURLToPath2(import.meta.url) === process.argv[1]) {
|
|
|
32604
34065
|
}
|
|
32605
34066
|
});
|
|
32606
34067
|
if (process.stdout.writableLength > 0) {
|
|
32607
|
-
await new Promise((
|
|
34068
|
+
await new Promise((resolve3) => {
|
|
32608
34069
|
process.stdout.write("", () => {
|
|
32609
|
-
|
|
34070
|
+
resolve3();
|
|
32610
34071
|
});
|
|
32611
34072
|
});
|
|
32612
34073
|
}
|