@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/reconcile.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,13 +492,12 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
|
-
import { existsSync as
|
|
496
|
-
import { join as
|
|
495
|
+
import { existsSync as existsSync6 } from "fs";
|
|
496
|
+
import { join as join10 } from "path";
|
|
497
497
|
|
|
498
|
-
// ../../packages/persistence/src/
|
|
499
|
-
import {
|
|
500
|
-
import { join
|
|
501
|
-
import { DatabaseSync } from "node:sqlite";
|
|
498
|
+
// ../../packages/persistence/src/control-plane-credential.ts
|
|
499
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
|
|
500
|
+
import { join } from "path";
|
|
502
501
|
|
|
503
502
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
504
503
|
var SQLITE_MIGRATIONS = [
|
|
@@ -16205,6 +16204,125 @@ var ConfigScanRecord = external_exports.object({
|
|
|
16205
16204
|
findings: external_exports.array(ConfigPostureFindingInput).optional()
|
|
16206
16205
|
});
|
|
16207
16206
|
|
|
16207
|
+
// ../../packages/schema/src/zod/control-plane.ts
|
|
16208
|
+
var ATTACHED_CREDENTIAL_FILENAME = "control-plane-credential.json";
|
|
16209
|
+
var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
|
|
16210
|
+
var AttachedCredential = external_exports.object({
|
|
16211
|
+
specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
|
|
16212
|
+
// The control-plane endpoint this credential was minted against.
|
|
16213
|
+
endpoint: external_exports.string().min(1),
|
|
16214
|
+
// The bearer credential itself. Never logged, never rendered — status
|
|
16215
|
+
// surfaces show `keyPrefix` and nothing else.
|
|
16216
|
+
apiKey: external_exports.string().min(1),
|
|
16217
|
+
// First few characters of the key, safe to display so a user can match the
|
|
16218
|
+
// credential against their organization's key list.
|
|
16219
|
+
keyPrefix: external_exports.string().min(1).max(16).optional(),
|
|
16220
|
+
mintedAt: external_exports.iso.datetime().optional()
|
|
16221
|
+
});
|
|
16222
|
+
var MAX_DATE_MS = 253402300799999;
|
|
16223
|
+
var MAX_INT4 = 2147483647;
|
|
16224
|
+
var StorePosturePack = external_exports.object({
|
|
16225
|
+
packId: external_exports.string().min(1),
|
|
16226
|
+
// 'namespace/packId'
|
|
16227
|
+
version: external_exports.string().min(1),
|
|
16228
|
+
enabled: external_exports.boolean(),
|
|
16229
|
+
// Stringified pass-through of the local store's `installed_packs.updated_at`
|
|
16230
|
+
// — the column format is store-version-dependent (epoch millis vs ISO), so
|
|
16231
|
+
// the wire shape assumes neither.
|
|
16232
|
+
updatedAt: external_exports.string().nullable()
|
|
16233
|
+
}).meta({ id: "StorePosturePack" });
|
|
16234
|
+
var StorePosturePolicyCounts = external_exports.object({
|
|
16235
|
+
total: external_exports.number().int().min(0),
|
|
16236
|
+
disabled: external_exports.number().int().min(0),
|
|
16237
|
+
// Exhaustive per-action map; the builder pre-fills every action with 0.
|
|
16238
|
+
//
|
|
16239
|
+
// Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
|
|
16240
|
+
// enforces exhaustiveness either way, but z.record emits `propertyNames` +
|
|
16241
|
+
// `additionalProperties` into a generated schema document, and a type
|
|
16242
|
+
// generator renders THAT with every key optional — a sender built against
|
|
16243
|
+
// the generated type would typecheck and still be rejected at runtime. An
|
|
16244
|
+
// explicit object emits `properties` + `required`, so generated types
|
|
16245
|
+
// demand all five.
|
|
16246
|
+
//
|
|
16247
|
+
// `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
|
|
16248
|
+
// ActionTaken member is a COMPILE error here instead of silent drift.
|
|
16249
|
+
// `.strict()` is load-bearing — it rejects an unknown action key, which a
|
|
16250
|
+
// bare object would silently STRIP, accepting a miscounted map as valid.
|
|
16251
|
+
byAction: external_exports.object({
|
|
16252
|
+
warn: external_exports.number().int().min(0),
|
|
16253
|
+
redact: external_exports.number().int().min(0),
|
|
16254
|
+
block: external_exports.number().int().min(0),
|
|
16255
|
+
allow: external_exports.number().int().min(0),
|
|
16256
|
+
log: external_exports.number().int().min(0)
|
|
16257
|
+
}).strict()
|
|
16258
|
+
}).meta({ id: "StorePosturePolicyCounts" });
|
|
16259
|
+
var StorePosturePlugin = external_exports.object({
|
|
16260
|
+
/** Package name of the reporting plugin. */
|
|
16261
|
+
package: external_exports.string().min(1).max(200),
|
|
16262
|
+
version: external_exports.string().min(1).max(64),
|
|
16263
|
+
/** Version of the bundled core, when the build records one separately. */
|
|
16264
|
+
ossVersion: external_exports.string().max(64).nullable(),
|
|
16265
|
+
/**
|
|
16266
|
+
* `version` of the policy bundle this machine last fetched. Bounded at 200
|
|
16267
|
+
* rather than the 64 a bare sha256 hex digest needs today, so a later
|
|
16268
|
+
* format with an algorithm prefix does not start rejecting the channel.
|
|
16269
|
+
*/
|
|
16270
|
+
policyBundleVersion: external_exports.string().max(200).nullable(),
|
|
16271
|
+
/** Epoch millis, on the CLIENT clock, of that fetch. */
|
|
16272
|
+
policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
|
|
16273
|
+
}).meta({ id: "StorePosturePlugin" });
|
|
16274
|
+
var StorePostureSnapshot = external_exports.object({
|
|
16275
|
+
deviceId: external_exports.guid(),
|
|
16276
|
+
hostname: external_exports.string().min(1).max(253),
|
|
16277
|
+
// Epoch millis on the CLIENT clock. Bounded by what a receiving store
|
|
16278
|
+
// accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
|
|
16279
|
+
capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
|
|
16280
|
+
// False is a measurement, not an error state: "no local store exists on
|
|
16281
|
+
// this machine".
|
|
16282
|
+
storePresent: external_exports.boolean(),
|
|
16283
|
+
schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
|
|
16284
|
+
// PRAGMA user_version
|
|
16285
|
+
findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
|
|
16286
|
+
// Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
|
|
16287
|
+
// bound does and does not do. Worth stating for these two specifically:
|
|
16288
|
+
// they are read from the local store's own ROWS rather than from this
|
|
16289
|
+
// machine's clock, so a damaged or hand-edited store is enough to produce
|
|
16290
|
+
// an out-of-range value with no clock skew involved.
|
|
16291
|
+
findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
|
|
16292
|
+
findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
|
|
16293
|
+
packs: external_exports.array(StorePosturePack).max(500),
|
|
16294
|
+
policyCounts: StorePosturePolicyCounts,
|
|
16295
|
+
// OPTIONAL, not nullable: a reporter that predates this member keeps
|
|
16296
|
+
// getting its 200 without a payload change.
|
|
16297
|
+
plugin: StorePosturePlugin.optional()
|
|
16298
|
+
}).meta({ id: "StorePostureSnapshot" });
|
|
16299
|
+
var CAPTURE_VERSION_PREFIX = "capture/";
|
|
16300
|
+
var RecordAuditEventRequest = AuditEventInput.extend({
|
|
16301
|
+
inspections: external_exports.array(ToolCallInspection).default([])
|
|
16302
|
+
}).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
|
|
16303
|
+
message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
|
|
16304
|
+
path: ["inspections"]
|
|
16305
|
+
}).meta({ id: "RecordAuditEventRequest" });
|
|
16306
|
+
var IngestAck = external_exports.object({
|
|
16307
|
+
accepted: external_exports.number().int().nonnegative(),
|
|
16308
|
+
duplicates: external_exports.number().int().nonnegative()
|
|
16309
|
+
});
|
|
16310
|
+
var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
|
|
16311
|
+
var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
|
|
16312
|
+
var PluginWhoami = external_exports.object({
|
|
16313
|
+
tenantName: printable(200),
|
|
16314
|
+
userEmail: printable(320),
|
|
16315
|
+
role: printable(64),
|
|
16316
|
+
keyKind: printable(64),
|
|
16317
|
+
serverTime: printable(64)
|
|
16318
|
+
});
|
|
16319
|
+
var ControlPlaneErrorBody = external_exports.object({
|
|
16320
|
+
error: external_exports.object({
|
|
16321
|
+
code: external_exports.string().optional(),
|
|
16322
|
+
message: external_exports.string().optional()
|
|
16323
|
+
}).optional()
|
|
16324
|
+
});
|
|
16325
|
+
|
|
16208
16326
|
// ../../packages/schema/src/zod/registry.ts
|
|
16209
16327
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
16210
16328
|
var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -16507,15 +16625,15 @@ function summaryToDetectionListItem(s) {
|
|
|
16507
16625
|
}
|
|
16508
16626
|
function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
16509
16627
|
const rules = row.rules.flatMap((r) => {
|
|
16510
|
-
const
|
|
16511
|
-
if (!
|
|
16628
|
+
const parsed2 = Matcher.safeParse(r.matcher);
|
|
16629
|
+
if (!parsed2.success) return [];
|
|
16512
16630
|
return [
|
|
16513
16631
|
{
|
|
16514
16632
|
id: r.id,
|
|
16515
16633
|
name: r.name,
|
|
16516
16634
|
category: r.category,
|
|
16517
16635
|
severity: r.severity,
|
|
16518
|
-
matcher:
|
|
16636
|
+
matcher: parsed2.data
|
|
16519
16637
|
}
|
|
16520
16638
|
];
|
|
16521
16639
|
});
|
|
@@ -17161,8 +17279,8 @@ function toApiAction(dbVal) {
|
|
|
17161
17279
|
}
|
|
17162
17280
|
function toApiCategory(dbVal) {
|
|
17163
17281
|
if (dbVal === "code_context") return "source_code";
|
|
17164
|
-
const
|
|
17165
|
-
return
|
|
17282
|
+
const parsed2 = FindingCategory.safeParse(dbVal);
|
|
17283
|
+
return parsed2.success ? parsed2.data : "custom";
|
|
17166
17284
|
}
|
|
17167
17285
|
function toApiProvider(sourceTool) {
|
|
17168
17286
|
return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
|
|
@@ -17799,6 +17917,9 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17799
17917
|
function defaultWorkspaceSettings() {
|
|
17800
17918
|
return WorkspaceSettings.parse({});
|
|
17801
17919
|
}
|
|
17920
|
+
function isAttached(settings) {
|
|
17921
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
17922
|
+
}
|
|
17802
17923
|
function toInventoryRow(input, id, now) {
|
|
17803
17924
|
return {
|
|
17804
17925
|
id,
|
|
@@ -18066,8 +18187,8 @@ function builtinPolicyIsReversible(id) {
|
|
|
18066
18187
|
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
18067
18188
|
}
|
|
18068
18189
|
function policyIdIsReversible(policyId) {
|
|
18069
|
-
const
|
|
18070
|
-
const id =
|
|
18190
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18191
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
18071
18192
|
return builtinPolicyIsReversible(id);
|
|
18072
18193
|
}
|
|
18073
18194
|
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
@@ -18078,8 +18199,8 @@ var BUILTIN_POLICIES = Object.fromEntries(
|
|
|
18078
18199
|
);
|
|
18079
18200
|
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
18080
18201
|
function policyIdToAction(policyId) {
|
|
18081
|
-
const
|
|
18082
|
-
const id =
|
|
18202
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18203
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
18083
18204
|
return BUILTIN_POLICIES[id].action;
|
|
18084
18205
|
}
|
|
18085
18206
|
var UsedByItem = external_exports.object({
|
|
@@ -18522,53 +18643,6 @@ function reviewSeverityRank(reasons) {
|
|
|
18522
18643
|
return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
|
|
18523
18644
|
}
|
|
18524
18645
|
|
|
18525
|
-
// ../../packages/persistence/src/ids.ts
|
|
18526
|
-
import { createHash } from "crypto";
|
|
18527
|
-
function sha256Hex(input) {
|
|
18528
|
-
return createHash("sha256").update(input).digest("hex");
|
|
18529
|
-
}
|
|
18530
|
-
function inventoryId(objectType, identityKey) {
|
|
18531
|
-
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
18532
|
-
}
|
|
18533
|
-
function sourceProjectId(url2) {
|
|
18534
|
-
return sha256Hex(canonicalIdentity(["source_project", url2]));
|
|
18535
|
-
}
|
|
18536
|
-
function classifiedDataId(cls) {
|
|
18537
|
-
return sha256Hex(canonicalIdentity(["classified_data", cls]));
|
|
18538
|
-
}
|
|
18539
|
-
function inspectionDefinitionId(ruleId, version2) {
|
|
18540
|
-
return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
|
|
18541
|
-
}
|
|
18542
|
-
function llmCallId(sessionId, messageId) {
|
|
18543
|
-
return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
|
|
18544
|
-
}
|
|
18545
|
-
function toolCallId(sessionId, toolUseId) {
|
|
18546
|
-
return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
|
|
18547
|
-
}
|
|
18548
|
-
function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
|
|
18549
|
-
return sha256Hex(
|
|
18550
|
-
canonicalIdentity([
|
|
18551
|
-
"inspection_finding",
|
|
18552
|
-
auditEventId,
|
|
18553
|
-
ruleId,
|
|
18554
|
-
String(spanStart),
|
|
18555
|
-
String(spanEnd)
|
|
18556
|
-
])
|
|
18557
|
-
);
|
|
18558
|
-
}
|
|
18559
|
-
var NO_SESSION = "no_session";
|
|
18560
|
-
var NO_PATH = "no_path";
|
|
18561
|
-
function captureId(sessionId, contentHash, filePath = null) {
|
|
18562
|
-
return sha256Hex(
|
|
18563
|
-
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
18564
|
-
);
|
|
18565
|
-
}
|
|
18566
|
-
|
|
18567
|
-
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18568
|
-
import { randomUUID } from "crypto";
|
|
18569
|
-
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18570
|
-
import { basename, dirname, join } from "path";
|
|
18571
|
-
|
|
18572
18646
|
// ../../packages/persistence/src/paths.ts
|
|
18573
18647
|
import {
|
|
18574
18648
|
chmodSync,
|
|
@@ -18694,7 +18768,123 @@ function publishByLink(tmp, file2, data) {
|
|
|
18694
18768
|
}
|
|
18695
18769
|
}
|
|
18696
18770
|
|
|
18771
|
+
// ../../packages/persistence/src/control-plane-credential.ts
|
|
18772
|
+
function controlPlaneCredentialPath(settingsDir2) {
|
|
18773
|
+
return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
|
|
18774
|
+
}
|
|
18775
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
18776
|
+
function isSafeEndpoint(endpoint) {
|
|
18777
|
+
let parsed2;
|
|
18778
|
+
try {
|
|
18779
|
+
parsed2 = new URL(endpoint);
|
|
18780
|
+
} catch {
|
|
18781
|
+
return false;
|
|
18782
|
+
}
|
|
18783
|
+
if (parsed2.protocol === "https:") return true;
|
|
18784
|
+
return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
|
|
18785
|
+
}
|
|
18786
|
+
function repairOrRefuseMode(file2) {
|
|
18787
|
+
const link = lstatSync2(file2, { throwIfNoEntry: false });
|
|
18788
|
+
if (link === void 0) return "absent";
|
|
18789
|
+
if (link.isSymbolicLink()) return "untrusted";
|
|
18790
|
+
const stat = statSync(file2, { throwIfNoEntry: false });
|
|
18791
|
+
if (stat === void 0) return "absent";
|
|
18792
|
+
const uid = process.getuid?.();
|
|
18793
|
+
if (uid !== void 0 && stat.uid !== uid) return "untrusted";
|
|
18794
|
+
if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
|
|
18795
|
+
try {
|
|
18796
|
+
chmodSync2(file2, DATA_FILE_MODE);
|
|
18797
|
+
} catch {
|
|
18798
|
+
return "untrusted";
|
|
18799
|
+
}
|
|
18800
|
+
}
|
|
18801
|
+
return "ok";
|
|
18802
|
+
}
|
|
18803
|
+
function readControlPlaneCredentialState(settingsDir2, connection) {
|
|
18804
|
+
const file2 = controlPlaneCredentialPath(settingsDir2);
|
|
18805
|
+
let raw;
|
|
18806
|
+
const gate = repairOrRefuseMode(file2);
|
|
18807
|
+
if (gate === "absent") return { usable: false, reason: "absent" };
|
|
18808
|
+
if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
|
|
18809
|
+
try {
|
|
18810
|
+
raw = readFileSync(file2, "utf8");
|
|
18811
|
+
} catch (err) {
|
|
18812
|
+
const code = err.code;
|
|
18813
|
+
return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
|
|
18814
|
+
}
|
|
18815
|
+
let parsed2;
|
|
18816
|
+
try {
|
|
18817
|
+
parsed2 = JSON.parse(raw);
|
|
18818
|
+
} catch {
|
|
18819
|
+
return { usable: false, reason: "malformed" };
|
|
18820
|
+
}
|
|
18821
|
+
const result = AttachedCredential.safeParse(parsed2);
|
|
18822
|
+
if (!result.success) return { usable: false, reason: "malformed" };
|
|
18823
|
+
if (!isSafeEndpoint(result.data.endpoint)) {
|
|
18824
|
+
return { usable: false, reason: "unsafe-endpoint" };
|
|
18825
|
+
}
|
|
18826
|
+
if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
|
|
18827
|
+
return {
|
|
18828
|
+
usable: false,
|
|
18829
|
+
reason: "endpoint-mismatch",
|
|
18830
|
+
credentialEndpoint: result.data.endpoint,
|
|
18831
|
+
settingsEndpoint: connection.endpoint
|
|
18832
|
+
};
|
|
18833
|
+
}
|
|
18834
|
+
return { usable: true, credential: result.data };
|
|
18835
|
+
}
|
|
18836
|
+
|
|
18837
|
+
// ../../packages/persistence/src/database.ts
|
|
18838
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
18839
|
+
import { join as join3, sep } from "path";
|
|
18840
|
+
import { DatabaseSync } from "node:sqlite";
|
|
18841
|
+
|
|
18842
|
+
// ../../packages/persistence/src/ids.ts
|
|
18843
|
+
import { createHash } from "crypto";
|
|
18844
|
+
function sha256Hex(input) {
|
|
18845
|
+
return createHash("sha256").update(input).digest("hex");
|
|
18846
|
+
}
|
|
18847
|
+
function inventoryId(objectType, identityKey) {
|
|
18848
|
+
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
18849
|
+
}
|
|
18850
|
+
function sourceProjectId(url2) {
|
|
18851
|
+
return sha256Hex(canonicalIdentity(["source_project", url2]));
|
|
18852
|
+
}
|
|
18853
|
+
function classifiedDataId(cls) {
|
|
18854
|
+
return sha256Hex(canonicalIdentity(["classified_data", cls]));
|
|
18855
|
+
}
|
|
18856
|
+
function inspectionDefinitionId(ruleId, version2) {
|
|
18857
|
+
return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
|
|
18858
|
+
}
|
|
18859
|
+
function llmCallId(sessionId, messageId) {
|
|
18860
|
+
return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
|
|
18861
|
+
}
|
|
18862
|
+
function toolCallId(sessionId, toolUseId) {
|
|
18863
|
+
return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
|
|
18864
|
+
}
|
|
18865
|
+
function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
|
|
18866
|
+
return sha256Hex(
|
|
18867
|
+
canonicalIdentity([
|
|
18868
|
+
"inspection_finding",
|
|
18869
|
+
auditEventId,
|
|
18870
|
+
ruleId,
|
|
18871
|
+
String(spanStart),
|
|
18872
|
+
String(spanEnd)
|
|
18873
|
+
])
|
|
18874
|
+
);
|
|
18875
|
+
}
|
|
18876
|
+
var NO_SESSION = "no_session";
|
|
18877
|
+
var NO_PATH = "no_path";
|
|
18878
|
+
function captureId(sessionId, contentHash, filePath = null) {
|
|
18879
|
+
return sha256Hex(
|
|
18880
|
+
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
18881
|
+
);
|
|
18882
|
+
}
|
|
18883
|
+
|
|
18697
18884
|
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18885
|
+
import { randomUUID } from "crypto";
|
|
18886
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
|
|
18887
|
+
import { basename, dirname, join as join2 } from "path";
|
|
18698
18888
|
function backupPath(file2, tag) {
|
|
18699
18889
|
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18700
18890
|
}
|
|
@@ -18704,15 +18894,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
|
|
|
18704
18894
|
var SNAPSHOT_STAGING_COPY = "copy";
|
|
18705
18895
|
function createSnapshotStaging(backup) {
|
|
18706
18896
|
const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
|
|
18707
|
-
|
|
18897
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18708
18898
|
mkdirOwnerOnlySync(stage);
|
|
18709
18899
|
tightenDir(stage);
|
|
18710
|
-
return { stage, copy:
|
|
18900
|
+
return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
|
|
18711
18901
|
}
|
|
18712
18902
|
function idleMs(entry) {
|
|
18713
|
-
for (const candidate of [
|
|
18903
|
+
for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
|
|
18714
18904
|
try {
|
|
18715
|
-
return Date.now() -
|
|
18905
|
+
return Date.now() - statSync2(candidate).mtimeMs;
|
|
18716
18906
|
} catch {
|
|
18717
18907
|
}
|
|
18718
18908
|
}
|
|
@@ -18729,11 +18919,11 @@ function reapStalePartials(file2) {
|
|
|
18729
18919
|
}
|
|
18730
18920
|
for (const name of entries) {
|
|
18731
18921
|
if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
|
|
18732
|
-
const staging =
|
|
18922
|
+
const staging = join2(dir, name);
|
|
18733
18923
|
try {
|
|
18734
18924
|
const idle = idleMs(staging);
|
|
18735
18925
|
if (idle !== null && idle > STALE_PARTIAL_MS) {
|
|
18736
|
-
|
|
18926
|
+
rmSync3(staging, { recursive: true, force: true });
|
|
18737
18927
|
}
|
|
18738
18928
|
} catch {
|
|
18739
18929
|
}
|
|
@@ -18747,13 +18937,13 @@ function snapshotStore(db, backup) {
|
|
|
18747
18937
|
renameSync2(copy, backup);
|
|
18748
18938
|
} catch (error51) {
|
|
18749
18939
|
try {
|
|
18750
|
-
|
|
18940
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18751
18941
|
} catch {
|
|
18752
18942
|
}
|
|
18753
18943
|
throw error51;
|
|
18754
18944
|
}
|
|
18755
18945
|
try {
|
|
18756
|
-
|
|
18946
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18757
18947
|
} catch {
|
|
18758
18948
|
}
|
|
18759
18949
|
}
|
|
@@ -18768,7 +18958,7 @@ function moveStoreAside(file2, backup) {
|
|
|
18768
18958
|
renameSync2(sidecar, moved);
|
|
18769
18959
|
undo.push([moved, sidecar]);
|
|
18770
18960
|
} catch {
|
|
18771
|
-
|
|
18961
|
+
rmSync3(sidecar, { force: true });
|
|
18772
18962
|
}
|
|
18773
18963
|
}
|
|
18774
18964
|
} catch (error51) {
|
|
@@ -18784,14 +18974,14 @@ function moveStoreAside(file2, backup) {
|
|
|
18784
18974
|
}
|
|
18785
18975
|
function discardStore(file2, backup) {
|
|
18786
18976
|
try {
|
|
18787
|
-
|
|
18977
|
+
rmSync3(file2, { force: true });
|
|
18788
18978
|
for (const sidecar of dbSidecars(file2)) {
|
|
18789
|
-
|
|
18979
|
+
rmSync3(sidecar, { force: true });
|
|
18790
18980
|
}
|
|
18791
18981
|
} catch (error51) {
|
|
18792
18982
|
if (existsSync(file2)) {
|
|
18793
18983
|
try {
|
|
18794
|
-
|
|
18984
|
+
rmSync3(backup, { force: true });
|
|
18795
18985
|
} catch {
|
|
18796
18986
|
}
|
|
18797
18987
|
}
|
|
@@ -19023,10 +19213,31 @@ function applyMigrations(db, file2) {
|
|
|
19023
19213
|
if (drained) applyLegacyDropMigration(db, file2);
|
|
19024
19214
|
}
|
|
19025
19215
|
}
|
|
19216
|
+
function readLegacyTables(db) {
|
|
19217
|
+
let holdsRows = false;
|
|
19218
|
+
const marks = [];
|
|
19219
|
+
for (const table of ["events", "findings"]) {
|
|
19220
|
+
try {
|
|
19221
|
+
const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table}`).get();
|
|
19222
|
+
if (row === void 0) {
|
|
19223
|
+
holdsRows = true;
|
|
19224
|
+
marks.push(`${table}:unreadable`);
|
|
19225
|
+
continue;
|
|
19226
|
+
}
|
|
19227
|
+
if (row.n > 0) holdsRows = true;
|
|
19228
|
+
marks.push(`${table}:${String(row.n)}:${String(row.hi)}`);
|
|
19229
|
+
} catch {
|
|
19230
|
+
holdsRows = true;
|
|
19231
|
+
marks.push(`${table}:unreadable`);
|
|
19232
|
+
}
|
|
19233
|
+
}
|
|
19234
|
+
return { holdsRows, mark: marks.join("|") };
|
|
19235
|
+
}
|
|
19026
19236
|
function applyLegacyDropMigration(db, file2) {
|
|
19027
19237
|
const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
|
|
19028
19238
|
if (!migration) return;
|
|
19029
|
-
|
|
19239
|
+
const before = file2 === void 0 ? void 0 : readLegacyTables(db);
|
|
19240
|
+
if (file2 !== void 0 && before?.holdsRows === true) {
|
|
19030
19241
|
try {
|
|
19031
19242
|
backupBeforeLegacyDrop(db, file2);
|
|
19032
19243
|
} catch (error51) {
|
|
@@ -19040,6 +19251,12 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
19040
19251
|
() => {
|
|
19041
19252
|
const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
|
|
19042
19253
|
if (alreadyDropped) return;
|
|
19254
|
+
if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
|
|
19255
|
+
akaWarn(
|
|
19256
|
+
"legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
|
|
19257
|
+
);
|
|
19258
|
+
return;
|
|
19259
|
+
}
|
|
19043
19260
|
for (const statement of splitStatements(migration.sql)) {
|
|
19044
19261
|
db.exec(statement);
|
|
19045
19262
|
}
|
|
@@ -19394,8 +19611,8 @@ function safeJson(s, fallback) {
|
|
|
19394
19611
|
function parseJsonObject(s) {
|
|
19395
19612
|
if (s == null) return void 0;
|
|
19396
19613
|
try {
|
|
19397
|
-
const
|
|
19398
|
-
if (typeof
|
|
19614
|
+
const parsed2 = JSON.parse(s);
|
|
19615
|
+
if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
|
|
19399
19616
|
} catch {
|
|
19400
19617
|
}
|
|
19401
19618
|
return void 0;
|
|
@@ -19406,16 +19623,16 @@ function encodeKeysetCursor(payload) {
|
|
|
19406
19623
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19407
19624
|
}
|
|
19408
19625
|
function decodeKeysetCursor(cursor) {
|
|
19409
|
-
const
|
|
19410
|
-
if (
|
|
19626
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19627
|
+
if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19411
19628
|
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19412
19629
|
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19413
19630
|
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19414
19631
|
// outcome a cursor that does not decode must never produce, since the
|
|
19415
19632
|
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19416
19633
|
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19417
|
-
Number.isInteger(
|
|
19418
|
-
return
|
|
19634
|
+
Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
|
|
19635
|
+
return parsed2;
|
|
19419
19636
|
}
|
|
19420
19637
|
return null;
|
|
19421
19638
|
}
|
|
@@ -19480,18 +19697,18 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
19480
19697
|
};
|
|
19481
19698
|
function safeParseStringArray(raw) {
|
|
19482
19699
|
if (!raw) return [];
|
|
19483
|
-
const
|
|
19484
|
-
return Array.isArray(
|
|
19700
|
+
const parsed2 = safeJson(raw, null);
|
|
19701
|
+
return Array.isArray(parsed2) ? parsed2 : [];
|
|
19485
19702
|
}
|
|
19486
19703
|
var DEFAULT_HARNESS = HARNESS.ClaudeCode;
|
|
19487
19704
|
function toHarness(raw) {
|
|
19488
|
-
const
|
|
19489
|
-
return
|
|
19705
|
+
const parsed2 = Harness.safeParse(raw);
|
|
19706
|
+
return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
|
|
19490
19707
|
}
|
|
19491
19708
|
function resolveLifecycle(row, lastActivityMs, nowMs) {
|
|
19492
19709
|
if (row.status) {
|
|
19493
|
-
const
|
|
19494
|
-
if (
|
|
19710
|
+
const parsed2 = SessionStatus.safeParse(row.status);
|
|
19711
|
+
if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
|
|
19495
19712
|
}
|
|
19496
19713
|
if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
|
|
19497
19714
|
if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
|
|
@@ -20450,9 +20667,9 @@ var SqliteDetectionsRepository = class {
|
|
|
20450
20667
|
const ruleIds = /* @__PURE__ */ new Set();
|
|
20451
20668
|
for (const r of rows) {
|
|
20452
20669
|
if (intToBool(r.enabled)) active += 1;
|
|
20453
|
-
const
|
|
20454
|
-
rules +=
|
|
20455
|
-
for (const rule of
|
|
20670
|
+
const parsed2 = parseRules(r.rulesJson);
|
|
20671
|
+
rules += parsed2.length;
|
|
20672
|
+
for (const rule of parsed2) {
|
|
20456
20673
|
if (typeof rule.id === "string") ruleIds.add(rule.id);
|
|
20457
20674
|
}
|
|
20458
20675
|
}
|
|
@@ -20986,12 +21203,12 @@ function encodeGroupCursor(group) {
|
|
|
20986
21203
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20987
21204
|
}
|
|
20988
21205
|
function decodeGroupCursor(cursor) {
|
|
20989
|
-
const
|
|
20990
|
-
if (
|
|
21206
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
21207
|
+
if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
|
|
20991
21208
|
return {
|
|
20992
|
-
severity:
|
|
20993
|
-
latestDetectedAt:
|
|
20994
|
-
id:
|
|
21209
|
+
severity: parsed2.sev,
|
|
21210
|
+
latestDetectedAt: parsed2.t,
|
|
21211
|
+
id: parsed2.id
|
|
20995
21212
|
};
|
|
20996
21213
|
}
|
|
20997
21214
|
return null;
|
|
@@ -22125,16 +22342,16 @@ var SqliteInstalledPacksRepository = class {
|
|
|
22125
22342
|
continue;
|
|
22126
22343
|
}
|
|
22127
22344
|
for (const entry of raw) {
|
|
22128
|
-
const
|
|
22129
|
-
if (
|
|
22130
|
-
out.rules.push(
|
|
22131
|
-
out.ruleActions.set(
|
|
22132
|
-
out.ruleVersions.set(
|
|
22133
|
-
if (reversible) out.reversibleRules.add(
|
|
22134
|
-
else out.reversibleRules.delete(
|
|
22345
|
+
const parsed2 = Rule.safeParse(entry);
|
|
22346
|
+
if (parsed2.success) {
|
|
22347
|
+
out.rules.push(parsed2.data);
|
|
22348
|
+
out.ruleActions.set(parsed2.data.id, action);
|
|
22349
|
+
out.ruleVersions.set(parsed2.data.id, row.version);
|
|
22350
|
+
if (reversible) out.reversibleRules.add(parsed2.data.id);
|
|
22351
|
+
else out.reversibleRules.delete(parsed2.data.id);
|
|
22135
22352
|
} else {
|
|
22136
22353
|
out.invalidRules += 1;
|
|
22137
|
-
reject(pack, printableRuleId(entry), firstIssueReason(
|
|
22354
|
+
reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
|
|
22138
22355
|
}
|
|
22139
22356
|
}
|
|
22140
22357
|
}
|
|
@@ -23524,15 +23741,15 @@ function encodeReuseCursor(payload) {
|
|
|
23524
23741
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23525
23742
|
}
|
|
23526
23743
|
function decodeReuseCursor(cursor) {
|
|
23527
|
-
const
|
|
23528
|
-
if (
|
|
23744
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23745
|
+
if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23529
23746
|
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23530
23747
|
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23531
23748
|
// malformed cursor must never produce, since restarting from the top is the
|
|
23532
23749
|
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23533
23750
|
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23534
|
-
Number.isInteger(
|
|
23535
|
-
return { occurrences:
|
|
23751
|
+
Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
|
|
23752
|
+
return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
|
|
23536
23753
|
}
|
|
23537
23754
|
return null;
|
|
23538
23755
|
}
|
|
@@ -25261,7 +25478,7 @@ function openAndInitialize(file2) {
|
|
|
25261
25478
|
}
|
|
25262
25479
|
function openLocalDatabase(dir) {
|
|
25263
25480
|
ensureDataDirSync(dir);
|
|
25264
|
-
const file2 =
|
|
25481
|
+
const file2 = join3(dir, DB_FILENAME);
|
|
25265
25482
|
reapStalePartials(file2);
|
|
25266
25483
|
const {
|
|
25267
25484
|
db,
|
|
@@ -25517,9 +25734,9 @@ import {
|
|
|
25517
25734
|
closeSync,
|
|
25518
25735
|
existsSync as existsSync2,
|
|
25519
25736
|
openSync,
|
|
25520
|
-
readFileSync,
|
|
25521
|
-
rmSync as
|
|
25522
|
-
statSync as
|
|
25737
|
+
readFileSync as readFileSync2,
|
|
25738
|
+
rmSync as rmSync4,
|
|
25739
|
+
statSync as statSync3,
|
|
25523
25740
|
writeFileSync as writeFileSync2
|
|
25524
25741
|
} from "fs";
|
|
25525
25742
|
import { hostname as hostname3 } from "os";
|
|
@@ -25530,20 +25747,20 @@ import { createHash as createHash3 } from "crypto";
|
|
|
25530
25747
|
|
|
25531
25748
|
// ../../packages/persistence/src/fingerprint.ts
|
|
25532
25749
|
import { createHmac, randomBytes } from "crypto";
|
|
25533
|
-
import { existsSync as existsSync3, readFileSync as
|
|
25534
|
-
import { join as
|
|
25750
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
25751
|
+
import { join as join4 } from "path";
|
|
25535
25752
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
25536
25753
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
25537
25754
|
var KEY_MATERIAL_BYTES = 32;
|
|
25538
25755
|
function keyFilePath(dataDir2) {
|
|
25539
|
-
return
|
|
25756
|
+
return join4(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
25540
25757
|
}
|
|
25541
25758
|
function parseKeyFile(raw) {
|
|
25542
|
-
const
|
|
25543
|
-
if (typeof
|
|
25759
|
+
const parsed2 = JSON.parse(raw);
|
|
25760
|
+
if (typeof parsed2 !== "object" || parsed2 === null) {
|
|
25544
25761
|
throw new Error("exception key file is corrupt: not a JSON object");
|
|
25545
25762
|
}
|
|
25546
|
-
const { version: version2, material } =
|
|
25763
|
+
const { version: version2, material } = parsed2;
|
|
25547
25764
|
if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
|
|
25548
25765
|
throw new Error("exception key file is corrupt: bad version");
|
|
25549
25766
|
}
|
|
@@ -25574,7 +25791,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
25574
25791
|
}
|
|
25575
25792
|
};
|
|
25576
25793
|
function storedKeyVersionFloor(dataDir2) {
|
|
25577
|
-
const file2 =
|
|
25794
|
+
const file2 = join4(dataDir2, DB_FILENAME);
|
|
25578
25795
|
if (!existsSync3(file2)) return 0;
|
|
25579
25796
|
let db;
|
|
25580
25797
|
try {
|
|
@@ -25629,7 +25846,7 @@ function occupantMessage(file2, kind) {
|
|
|
25629
25846
|
function readFingerprintKey(dataDir2) {
|
|
25630
25847
|
let raw;
|
|
25631
25848
|
try {
|
|
25632
|
-
raw =
|
|
25849
|
+
raw = readFileSync3(keyFilePath(dataDir2), "utf8");
|
|
25633
25850
|
} catch (err) {
|
|
25634
25851
|
if (err.code === "ENOENT") return null;
|
|
25635
25852
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -25655,21 +25872,25 @@ function fingerprintValue(key, raw) {
|
|
|
25655
25872
|
import { renameSync as renameSync3 } from "fs";
|
|
25656
25873
|
import { mkdir } from "fs/promises";
|
|
25657
25874
|
import { homedir } from "os";
|
|
25658
|
-
import { join as
|
|
25875
|
+
import { join as join5 } from "path";
|
|
25659
25876
|
function defaultDataDir() {
|
|
25660
|
-
return
|
|
25877
|
+
return join5(homedir(), ".aka");
|
|
25661
25878
|
}
|
|
25662
25879
|
function settingsDir(base = defaultDataDir()) {
|
|
25663
|
-
return
|
|
25880
|
+
return join5(base, "settings");
|
|
25664
25881
|
}
|
|
25665
25882
|
function dataDir(base = defaultDataDir()) {
|
|
25666
|
-
return
|
|
25883
|
+
return join5(base, "data");
|
|
25667
25884
|
}
|
|
25668
25885
|
function dbPath(base = defaultDataDir()) {
|
|
25669
|
-
return
|
|
25886
|
+
return join5(dataDir(base), "aka.db");
|
|
25670
25887
|
}
|
|
25671
25888
|
function keysDir(base = defaultDataDir()) {
|
|
25672
|
-
return
|
|
25889
|
+
return join5(base, "keys");
|
|
25890
|
+
}
|
|
25891
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
25892
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
25893
|
+
tightenDir(dir);
|
|
25673
25894
|
}
|
|
25674
25895
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
25675
25896
|
ensureDataDirSync(dir);
|
|
@@ -25682,8 +25903,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25682
25903
|
for (const { name, dest } of moves) {
|
|
25683
25904
|
try {
|
|
25684
25905
|
ensureDataDirSync(dest);
|
|
25685
|
-
const moved =
|
|
25686
|
-
renameSync3(
|
|
25906
|
+
const moved = join5(dest, name);
|
|
25907
|
+
renameSync3(join5(base, name), moved);
|
|
25687
25908
|
tightenFile(moved);
|
|
25688
25909
|
} catch {
|
|
25689
25910
|
}
|
|
@@ -25691,7 +25912,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25691
25912
|
}
|
|
25692
25913
|
|
|
25693
25914
|
// ../../packages/persistence/src/managed-settings.ts
|
|
25694
|
-
import { readFileSync as
|
|
25915
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
25695
25916
|
import { posix, win32 } from "path";
|
|
25696
25917
|
function managedSettingsPaths(platform2 = process.platform) {
|
|
25697
25918
|
if (platform2 === "darwin") {
|
|
@@ -25709,14 +25930,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
|
|
|
25709
25930
|
for (const path of paths) {
|
|
25710
25931
|
let text;
|
|
25711
25932
|
try {
|
|
25712
|
-
text =
|
|
25933
|
+
text = readFileSync4(path, "utf8");
|
|
25713
25934
|
} catch {
|
|
25714
25935
|
continue;
|
|
25715
25936
|
}
|
|
25716
25937
|
const record2 = parseJsonObject(text);
|
|
25717
25938
|
if (!record2) continue;
|
|
25718
|
-
const
|
|
25719
|
-
if (
|
|
25939
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
25940
|
+
if (parsed2.success) return parsed2.data;
|
|
25720
25941
|
}
|
|
25721
25942
|
return null;
|
|
25722
25943
|
}
|
|
@@ -25756,14 +25977,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
|
|
|
25756
25977
|
}
|
|
25757
25978
|
|
|
25758
25979
|
// ../../packages/persistence/src/settings.ts
|
|
25759
|
-
import { readFileSync as
|
|
25760
|
-
import { join as
|
|
25980
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
25981
|
+
import { join as join6 } from "path";
|
|
25761
25982
|
var SETTINGS_FILENAME = "settings.json";
|
|
25762
25983
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
25763
25984
|
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
25764
25985
|
}
|
|
25765
25986
|
function readUserSettings(base) {
|
|
25766
|
-
const record2 = readJson(
|
|
25987
|
+
const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
|
|
25767
25988
|
if (!record2) return defaultWorkspaceSettings();
|
|
25768
25989
|
try {
|
|
25769
25990
|
return WorkspaceSettings.parse(record2);
|
|
@@ -25774,13 +25995,17 @@ function readUserSettings(base) {
|
|
|
25774
25995
|
function readJson(file2) {
|
|
25775
25996
|
let text;
|
|
25776
25997
|
try {
|
|
25777
|
-
text =
|
|
25998
|
+
text = readFileSync5(file2, "utf8");
|
|
25778
25999
|
} catch {
|
|
25779
26000
|
return null;
|
|
25780
26001
|
}
|
|
25781
26002
|
return parseJsonObject(text) ?? null;
|
|
25782
26003
|
}
|
|
25783
26004
|
|
|
26005
|
+
// ../../packages/persistence/src/store-symlinks.ts
|
|
26006
|
+
import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
26007
|
+
import { dirname as dirname2, join as join7, resolve } from "path";
|
|
26008
|
+
|
|
25784
26009
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
25785
26010
|
import {
|
|
25786
26011
|
createCipheriv,
|
|
@@ -25892,8 +26117,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
25892
26117
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25893
26118
|
import { execFileSync } from "child_process";
|
|
25894
26119
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
25895
|
-
import { chmodSync as
|
|
25896
|
-
import { join as
|
|
26120
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
26121
|
+
import { join as join8 } from "path";
|
|
25897
26122
|
var VAULT_OCCUPANT_REASON = {
|
|
25898
26123
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25899
26124
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -25912,11 +26137,11 @@ var KEY_MATERIAL_BYTES2 = 32;
|
|
|
25912
26137
|
var KEYCHAIN_SERVICE = "aka-vault";
|
|
25913
26138
|
var KEYCHAIN_ACCOUNT = "keyring";
|
|
25914
26139
|
function parseKeyring(raw) {
|
|
25915
|
-
const
|
|
25916
|
-
if (typeof
|
|
26140
|
+
const parsed2 = JSON.parse(raw);
|
|
26141
|
+
if (typeof parsed2 !== "object" || parsed2 === null) {
|
|
25917
26142
|
throw new Error("vault key file is corrupt: not a JSON object");
|
|
25918
26143
|
}
|
|
25919
|
-
const { current, keys } =
|
|
26144
|
+
const { current, keys } = parsed2;
|
|
25920
26145
|
if (typeof current !== "number" || !Number.isInteger(current) || current < 1) {
|
|
25921
26146
|
throw new Error("vault key file is corrupt: bad current version");
|
|
25922
26147
|
}
|
|
@@ -25992,28 +26217,28 @@ function claimRotationLock(lock, owner) {
|
|
|
25992
26217
|
throw asError(err);
|
|
25993
26218
|
}
|
|
25994
26219
|
try {
|
|
25995
|
-
writeFileSync3(
|
|
26220
|
+
writeFileSync3(join8(lock, LOCK_OWNER_FILE), `${owner}
|
|
25996
26221
|
`, { mode: DATA_FILE_MODE });
|
|
25997
26222
|
return true;
|
|
25998
26223
|
} catch (err) {
|
|
25999
|
-
|
|
26224
|
+
rmSync5(lock, { recursive: true, force: true });
|
|
26000
26225
|
throw asError(err);
|
|
26001
26226
|
}
|
|
26002
26227
|
}
|
|
26003
26228
|
function acquireRotationLock(keysDir2) {
|
|
26004
|
-
const lock =
|
|
26229
|
+
const lock = join8(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
26005
26230
|
const owner = randomBytes2(16).toString("hex");
|
|
26006
26231
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
26007
26232
|
let held;
|
|
26008
26233
|
try {
|
|
26009
|
-
held =
|
|
26234
|
+
held = statSync5(lock);
|
|
26010
26235
|
} catch {
|
|
26011
26236
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26012
26237
|
}
|
|
26013
26238
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
26014
26239
|
const aside = `${lock}.stale.${owner}`;
|
|
26015
26240
|
try {
|
|
26016
|
-
const now =
|
|
26241
|
+
const now = statSync5(lock);
|
|
26017
26242
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
26018
26243
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26019
26244
|
}
|
|
@@ -26022,17 +26247,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
26022
26247
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
26023
26248
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
26024
26249
|
}
|
|
26025
|
-
|
|
26250
|
+
rmSync5(aside, { recursive: true, force: true });
|
|
26026
26251
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
26027
26252
|
return { lock, owner };
|
|
26028
26253
|
}
|
|
26029
26254
|
function releaseRotationLock(lease) {
|
|
26030
26255
|
try {
|
|
26031
|
-
if (
|
|
26256
|
+
if (readFileSync6(join8(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
26032
26257
|
} catch {
|
|
26033
26258
|
return;
|
|
26034
26259
|
}
|
|
26035
|
-
|
|
26260
|
+
rmSync5(lease.lock, { recursive: true, force: true });
|
|
26036
26261
|
}
|
|
26037
26262
|
function withRotationLock(keysDir2, work) {
|
|
26038
26263
|
ensureDataDirSync(keysDir2);
|
|
@@ -26049,7 +26274,7 @@ var FileKeyProvider = class {
|
|
|
26049
26274
|
this.#keysDir = keysDir2;
|
|
26050
26275
|
}
|
|
26051
26276
|
get filePath() {
|
|
26052
|
-
return
|
|
26277
|
+
return join8(this.#keysDir, VAULT_KEY_FILENAME);
|
|
26053
26278
|
}
|
|
26054
26279
|
loadOrCreate() {
|
|
26055
26280
|
return asAsync(() => {
|
|
@@ -26079,7 +26304,7 @@ var FileKeyProvider = class {
|
|
|
26079
26304
|
#read() {
|
|
26080
26305
|
let raw;
|
|
26081
26306
|
try {
|
|
26082
|
-
raw =
|
|
26307
|
+
raw = readFileSync6(this.filePath, "utf8");
|
|
26083
26308
|
} catch (err) {
|
|
26084
26309
|
if (err.code === "ENOENT") return null;
|
|
26085
26310
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -26136,7 +26361,7 @@ var FileKeyProvider = class {
|
|
|
26136
26361
|
};
|
|
26137
26362
|
function tightenFileMode(file2) {
|
|
26138
26363
|
try {
|
|
26139
|
-
|
|
26364
|
+
chmodSync3(file2, DATA_FILE_MODE);
|
|
26140
26365
|
} catch {
|
|
26141
26366
|
}
|
|
26142
26367
|
}
|
|
@@ -26401,25 +26626,25 @@ var SecretVault = class {
|
|
|
26401
26626
|
* model. Every call that gets as far as an identified row writes an audit row.
|
|
26402
26627
|
*/
|
|
26403
26628
|
async detokenize(token, opts) {
|
|
26404
|
-
const
|
|
26405
|
-
if (!
|
|
26629
|
+
const parsed2 = parsePointer(token);
|
|
26630
|
+
if (!parsed2) return UNAVAILABLE;
|
|
26406
26631
|
let signKey;
|
|
26407
26632
|
try {
|
|
26408
|
-
const epoch = await this.#keys.materialFor(
|
|
26633
|
+
const epoch = await this.#keys.materialFor(parsed2.keyVersion);
|
|
26409
26634
|
signKey = deriveSubkeys(epoch.material).sign;
|
|
26410
26635
|
} catch {
|
|
26411
26636
|
return UNAVAILABLE;
|
|
26412
26637
|
}
|
|
26413
|
-
if (!verifyPointerTag(signKey,
|
|
26638
|
+
if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
|
|
26414
26639
|
return UNAVAILABLE;
|
|
26415
26640
|
}
|
|
26416
|
-
const pointerId = base32Encode(
|
|
26641
|
+
const pointerId = base32Encode(parsed2.pointerId);
|
|
26417
26642
|
const row = this.#repo.byPointerId(pointerId);
|
|
26418
26643
|
if (!row) {
|
|
26419
26644
|
this.#audit(pointerId, opts, "unavailable");
|
|
26420
26645
|
return UNAVAILABLE;
|
|
26421
26646
|
}
|
|
26422
|
-
if (row.category !==
|
|
26647
|
+
if (row.category !== parsed2.category) return UNAVAILABLE;
|
|
26423
26648
|
if (opts.target === "model") {
|
|
26424
26649
|
const grantId = opts.grantId;
|
|
26425
26650
|
const verify = this.#verifyGrant;
|
|
@@ -26456,7 +26681,7 @@ var SecretVault = class {
|
|
|
26456
26681
|
// moved the epoch past the one this token names, and a format bump may
|
|
26457
26682
|
// have moved the constant past the generation this row was sealed
|
|
26458
26683
|
// under — the AAD follows the row in both cases, never the token.
|
|
26459
|
-
bindingInput(row.keyVersion,
|
|
26684
|
+
bindingInput(row.keyVersion, parsed2.pointerId, row.category, row.formatVersion)
|
|
26460
26685
|
);
|
|
26461
26686
|
} catch {
|
|
26462
26687
|
raw = null;
|
|
@@ -26674,19 +26899,19 @@ var SecretVault = class {
|
|
|
26674
26899
|
// preview. Verifying needs the historical epoch's key, which is why these
|
|
26675
26900
|
// surfaces are async.
|
|
26676
26901
|
async #rowFor(token) {
|
|
26677
|
-
const
|
|
26678
|
-
if (!
|
|
26902
|
+
const parsed2 = parsePointer(token);
|
|
26903
|
+
if (!parsed2) return null;
|
|
26679
26904
|
try {
|
|
26680
|
-
const epoch = await this.#keys.materialFor(
|
|
26905
|
+
const epoch = await this.#keys.materialFor(parsed2.keyVersion);
|
|
26681
26906
|
const signKey = deriveSubkeys(epoch.material).sign;
|
|
26682
|
-
if (!verifyPointerTag(signKey,
|
|
26907
|
+
if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
|
|
26683
26908
|
return null;
|
|
26684
26909
|
}
|
|
26685
26910
|
} catch {
|
|
26686
26911
|
return null;
|
|
26687
26912
|
}
|
|
26688
|
-
const row = this.#repo.byPointerId(base32Encode(
|
|
26689
|
-
if (row?.category !==
|
|
26913
|
+
const row = this.#repo.byPointerId(base32Encode(parsed2.pointerId));
|
|
26914
|
+
if (row?.category !== parsed2.category) return null;
|
|
26690
26915
|
return row;
|
|
26691
26916
|
}
|
|
26692
26917
|
#audit(pointerId, opts, outcome) {
|
|
@@ -26706,13 +26931,13 @@ var SecretVault = class {
|
|
|
26706
26931
|
};
|
|
26707
26932
|
|
|
26708
26933
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
26709
|
-
import { existsSync as
|
|
26710
|
-
import { join as
|
|
26934
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
26935
|
+
import { join as join9 } from "path";
|
|
26711
26936
|
var MARKER = "warn-era-capped";
|
|
26712
26937
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
26713
26938
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
26714
|
-
const marker =
|
|
26715
|
-
if (
|
|
26939
|
+
const marker = join9(dataDir2, MARKER);
|
|
26940
|
+
if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
|
|
26716
26941
|
const capped = db.policies.capCategoryActions();
|
|
26717
26942
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
26718
26943
|
`, { mode: DATA_FILE_MODE });
|
|
@@ -26753,8 +26978,8 @@ function hostOf(url2) {
|
|
|
26753
26978
|
}
|
|
26754
26979
|
}
|
|
26755
26980
|
function resolveProvider() {
|
|
26756
|
-
const
|
|
26757
|
-
const env =
|
|
26981
|
+
const parsed2 = ProviderEnvSchema.safeParse(process.env);
|
|
26982
|
+
const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
|
|
26758
26983
|
if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
|
|
26759
26984
|
if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
|
|
26760
26985
|
const baseUrl = env.ANTHROPIC_BASE_URL;
|
|
@@ -26781,8 +27006,8 @@ function providerFromModelId(modelId) {
|
|
|
26781
27006
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
26782
27007
|
try {
|
|
26783
27008
|
ensureLayoutDirSync(base);
|
|
26784
|
-
const settingsFile =
|
|
26785
|
-
if (
|
|
27009
|
+
const settingsFile = join10(settingsDir(base), "settings.json");
|
|
27010
|
+
if (existsSync6(settingsFile)) tightenFile(settingsFile);
|
|
26786
27011
|
} catch {
|
|
26787
27012
|
}
|
|
26788
27013
|
migrateLegacyLayout(base);
|
|
@@ -26805,9 +27030,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
26805
27030
|
}
|
|
26806
27031
|
|
|
26807
27032
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
26808
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
27033
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
26809
27034
|
import { homedir as homedir2 } from "os";
|
|
26810
|
-
import { basename as basename3, join as
|
|
27035
|
+
import { basename as basename3, join as join12 } from "path";
|
|
26811
27036
|
|
|
26812
27037
|
// ../../packages/detections/src/egress/registry.ts
|
|
26813
27038
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -28497,10 +28722,10 @@ var localhost_ref_default = {
|
|
|
28497
28722
|
severity: "low",
|
|
28498
28723
|
matcher: {
|
|
28499
28724
|
type: "regex",
|
|
28500
|
-
pattern: "
|
|
28725
|
+
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_])",
|
|
28501
28726
|
flags: "g"
|
|
28502
28727
|
},
|
|
28503
|
-
examples: ["localhost", "127.0.0.1"]
|
|
28728
|
+
examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
|
|
28504
28729
|
};
|
|
28505
28730
|
|
|
28506
28731
|
// ../../rules/core-code-context/stack-trace.json
|
|
@@ -29851,8 +30076,8 @@ function scanText(text, ruleVersions) {
|
|
|
29851
30076
|
}
|
|
29852
30077
|
|
|
29853
30078
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
29854
|
-
import { existsSync as
|
|
29855
|
-
import { basename as basename2, dirname as
|
|
30079
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
|
|
30080
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join11, sep as sep2 } from "path";
|
|
29856
30081
|
function resolveRepoIdentity(cwd) {
|
|
29857
30082
|
try {
|
|
29858
30083
|
const root = findGitRoot(cwd);
|
|
@@ -29885,36 +30110,36 @@ function resolveRepoNwo(cwd) {
|
|
|
29885
30110
|
function findGitRoot(start) {
|
|
29886
30111
|
let dir = start;
|
|
29887
30112
|
for (; ; ) {
|
|
29888
|
-
if (
|
|
29889
|
-
const parent =
|
|
30113
|
+
if (existsSync7(join11(dir, ".git"))) return dir;
|
|
30114
|
+
const parent = dirname3(dir);
|
|
29890
30115
|
if (parent === dir) return void 0;
|
|
29891
30116
|
dir = parent;
|
|
29892
30117
|
}
|
|
29893
30118
|
}
|
|
29894
30119
|
function resolveGitContext(root) {
|
|
29895
|
-
const dotGit =
|
|
30120
|
+
const dotGit = join11(root, ".git");
|
|
29896
30121
|
try {
|
|
29897
|
-
if (
|
|
29898
|
-
return { configPath:
|
|
30122
|
+
if (statSync6(dotGit).isDirectory()) {
|
|
30123
|
+
return { configPath: join11(dotGit, "config"), headRoot: root };
|
|
29899
30124
|
}
|
|
29900
30125
|
} catch {
|
|
29901
30126
|
return void 0;
|
|
29902
30127
|
}
|
|
29903
30128
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
29904
30129
|
if (!target) return void 0;
|
|
29905
|
-
const gitdir = isAbsolute(target) ? target :
|
|
29906
|
-
if (
|
|
29907
|
-
return { configPath:
|
|
30130
|
+
const gitdir = isAbsolute(target) ? target : join11(root, target);
|
|
30131
|
+
if (existsSync7(join11(gitdir, "config"))) {
|
|
30132
|
+
return { configPath: join11(gitdir, "config"), headRoot: root };
|
|
29908
30133
|
}
|
|
29909
|
-
const commonRaw = safeRead(
|
|
30134
|
+
const commonRaw = safeRead(join11(gitdir, "commondir"))?.trim();
|
|
29910
30135
|
if (!commonRaw) return void 0;
|
|
29911
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
29912
|
-
const headRoot = basename2(commonGitDir) === ".git" ?
|
|
29913
|
-
return { configPath:
|
|
30136
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join11(gitdir, commonRaw);
|
|
30137
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
|
|
30138
|
+
return { configPath: join11(commonGitDir, "config"), headRoot };
|
|
29914
30139
|
}
|
|
29915
30140
|
function safeRead(path) {
|
|
29916
30141
|
try {
|
|
29917
|
-
return
|
|
30142
|
+
return readFileSync7(path, "utf8");
|
|
29918
30143
|
} catch {
|
|
29919
30144
|
return void 0;
|
|
29920
30145
|
}
|
|
@@ -29968,14 +30193,14 @@ function nwoFromUrl(url2) {
|
|
|
29968
30193
|
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
29969
30194
|
|
|
29970
30195
|
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29971
|
-
import { existsSync as
|
|
30196
|
+
import { existsSync as existsSync8 } from "fs";
|
|
29972
30197
|
import { fileURLToPath } from "url";
|
|
29973
30198
|
import { Worker } from "worker_threads";
|
|
29974
30199
|
|
|
29975
30200
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
29976
30201
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
29977
|
-
import { readFileSync as
|
|
29978
|
-
import { join as
|
|
30202
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
30203
|
+
import { join as join13 } from "path";
|
|
29979
30204
|
|
|
29980
30205
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
29981
30206
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -30007,16 +30232,16 @@ function resolveInventoryContext(input) {
|
|
|
30007
30232
|
}
|
|
30008
30233
|
|
|
30009
30234
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
30010
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
30011
|
-
import { join as
|
|
30235
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
30236
|
+
import { join as join14 } from "path";
|
|
30012
30237
|
|
|
30013
30238
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
30014
|
-
import { readdirSync as readdirSync3, realpathSync as
|
|
30015
|
-
import { basename as basename4, dirname as
|
|
30239
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
30240
|
+
import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
|
|
30016
30241
|
|
|
30017
30242
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
30018
|
-
import { existsSync as
|
|
30019
|
-
import { basename as basename5, join as
|
|
30243
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
30244
|
+
import { basename as basename5, join as join15 } from "path";
|
|
30020
30245
|
|
|
30021
30246
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30022
30247
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -30051,8 +30276,8 @@ import { randomUUID as randomUUID14 } from "crypto";
|
|
|
30051
30276
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
30052
30277
|
|
|
30053
30278
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
30054
|
-
import { mkdirSync as mkdirSync3, statSync as
|
|
30055
|
-
import { join as
|
|
30279
|
+
import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
30280
|
+
import { join as join16 } from "path";
|
|
30056
30281
|
|
|
30057
30282
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
30058
30283
|
function redactedPlaceholder(category) {
|
|
@@ -30354,8 +30579,1198 @@ var UNOPENABLE_VAULT = {
|
|
|
30354
30579
|
resolvePointerIdentity: () => Promise.resolve(null)
|
|
30355
30580
|
};
|
|
30356
30581
|
|
|
30357
|
-
// ../../packages/plugin-runtime/src/
|
|
30582
|
+
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
30583
|
+
function statusOf(err) {
|
|
30584
|
+
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
30585
|
+
const { status } = err;
|
|
30586
|
+
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
30587
|
+
return status >= 100 && status <= 599 ? status : null;
|
|
30588
|
+
}
|
|
30589
|
+
function classifyFailure(err) {
|
|
30590
|
+
switch (statusOf(err)) {
|
|
30591
|
+
case 401:
|
|
30592
|
+
return "unauthorized";
|
|
30593
|
+
case 403:
|
|
30594
|
+
return "forbidden";
|
|
30595
|
+
default:
|
|
30596
|
+
return "unreachable";
|
|
30597
|
+
}
|
|
30598
|
+
}
|
|
30599
|
+
|
|
30600
|
+
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
30601
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
30602
|
+
import { join as join17 } from "path";
|
|
30603
|
+
var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
30604
|
+
function forwardDropsPath(dataDir2) {
|
|
30605
|
+
return join17(dataDir2, FORWARD_DROPS_FILENAME);
|
|
30606
|
+
}
|
|
30607
|
+
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
30608
|
+
if (count <= 0) return;
|
|
30609
|
+
try {
|
|
30610
|
+
ensureDataDirSync(dataDir2);
|
|
30611
|
+
const previous = readForwardDrops(dataDir2);
|
|
30612
|
+
const next = {
|
|
30613
|
+
droppedForwards: (previous?.droppedForwards ?? 0) + count,
|
|
30614
|
+
lastDropAtMs: nowMs
|
|
30615
|
+
};
|
|
30616
|
+
writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
|
|
30617
|
+
`);
|
|
30618
|
+
} catch {
|
|
30619
|
+
}
|
|
30620
|
+
}
|
|
30621
|
+
function readForwardDrops(dataDir2) {
|
|
30622
|
+
try {
|
|
30623
|
+
const parsed2 = JSON.parse(readFileSync11(forwardDropsPath(dataDir2), "utf8"));
|
|
30624
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
30625
|
+
const record2 = parsed2;
|
|
30626
|
+
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
30627
|
+
return null;
|
|
30628
|
+
}
|
|
30629
|
+
if (record2.droppedForwards <= 0) return null;
|
|
30630
|
+
if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
|
|
30631
|
+
return null;
|
|
30632
|
+
}
|
|
30633
|
+
return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
|
|
30634
|
+
} catch {
|
|
30635
|
+
return null;
|
|
30636
|
+
}
|
|
30637
|
+
}
|
|
30638
|
+
|
|
30639
|
+
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
30358
30640
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
30641
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
30642
|
+
import { readFile, rename, writeFile } from "fs/promises";
|
|
30643
|
+
import { join as join18 } from "path";
|
|
30644
|
+
|
|
30645
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
30646
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
30647
|
+
function withTimeout(promise2, ms) {
|
|
30648
|
+
let timer;
|
|
30649
|
+
const timeout = new Promise((_, reject) => {
|
|
30650
|
+
timer = setTimeout(() => {
|
|
30651
|
+
reject(new Error("attached gateway request timed out"));
|
|
30652
|
+
}, ms);
|
|
30653
|
+
});
|
|
30654
|
+
promise2.catch(() => void 0);
|
|
30655
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
30656
|
+
clearTimeout(timer);
|
|
30657
|
+
});
|
|
30658
|
+
}
|
|
30659
|
+
|
|
30660
|
+
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
30661
|
+
function isInvalidRequest(err) {
|
|
30662
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
30663
|
+
}
|
|
30664
|
+
var FORWARD_BUDGET_MS = 1500;
|
|
30665
|
+
var DECISION_PATH_BUDGET_MS = 800;
|
|
30666
|
+
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
30667
|
+
var BREAKER_COOLDOWN_MS = 3e4;
|
|
30668
|
+
var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
|
|
30669
|
+
var FAILURES = /* @__PURE__ */ new Set([
|
|
30670
|
+
"unauthorized",
|
|
30671
|
+
"forbidden",
|
|
30672
|
+
"unreachable"
|
|
30673
|
+
]);
|
|
30674
|
+
var FORWARD_STATE_FILENAME = "attached-state.json";
|
|
30675
|
+
var STATE_FILENAME = FORWARD_STATE_FILENAME;
|
|
30676
|
+
function parseBreakerState(raw, nowMs) {
|
|
30677
|
+
try {
|
|
30678
|
+
const parsed2 = JSON.parse(raw);
|
|
30679
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
30680
|
+
const record2 = parsed2;
|
|
30681
|
+
const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
|
|
30682
|
+
const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
|
|
30683
|
+
const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
|
|
30684
|
+
return { consecutiveFailures: failures, openedAtMs, lastFailure };
|
|
30685
|
+
} catch {
|
|
30686
|
+
return null;
|
|
30687
|
+
}
|
|
30688
|
+
}
|
|
30689
|
+
function createForwardPolicy(deps) {
|
|
30690
|
+
const now = deps.now ?? (() => Date.now());
|
|
30691
|
+
const file2 = join18(deps.dir, STATE_FILENAME);
|
|
30692
|
+
let state = null;
|
|
30693
|
+
let loading = null;
|
|
30694
|
+
async function readState() {
|
|
30695
|
+
let raw;
|
|
30696
|
+
try {
|
|
30697
|
+
raw = await readFile(file2, "utf8");
|
|
30698
|
+
} catch {
|
|
30699
|
+
return { ...CLOSED };
|
|
30700
|
+
}
|
|
30701
|
+
return parseBreakerState(raw, now()) ?? { ...CLOSED };
|
|
30702
|
+
}
|
|
30703
|
+
async function load() {
|
|
30704
|
+
if (state !== null) return state;
|
|
30705
|
+
loading ??= readState().then((loaded) => {
|
|
30706
|
+
state = loaded;
|
|
30707
|
+
loading = null;
|
|
30708
|
+
return loaded;
|
|
30709
|
+
});
|
|
30710
|
+
return loading;
|
|
30711
|
+
}
|
|
30712
|
+
async function persist(next) {
|
|
30713
|
+
state = next;
|
|
30714
|
+
try {
|
|
30715
|
+
await ensureDataDir(deps.dir);
|
|
30716
|
+
const tmp = `${file2}.${randomUUID15()}.tmp`;
|
|
30717
|
+
await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
30718
|
+
await rename(tmp, file2);
|
|
30719
|
+
} catch {
|
|
30720
|
+
}
|
|
30721
|
+
}
|
|
30722
|
+
return {
|
|
30723
|
+
async run(op, opts) {
|
|
30724
|
+
const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
|
|
30725
|
+
let current;
|
|
30726
|
+
try {
|
|
30727
|
+
current = await load();
|
|
30728
|
+
} catch {
|
|
30729
|
+
current = { ...CLOSED };
|
|
30730
|
+
}
|
|
30731
|
+
const at = now();
|
|
30732
|
+
if (current.openedAtMs !== null) {
|
|
30733
|
+
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
30734
|
+
return { ok: false, reason: "breaker-open" };
|
|
30735
|
+
}
|
|
30736
|
+
await persist({
|
|
30737
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
30738
|
+
openedAtMs: at,
|
|
30739
|
+
lastFailure: current.lastFailure
|
|
30740
|
+
});
|
|
30741
|
+
}
|
|
30742
|
+
try {
|
|
30743
|
+
const value = await withTimeout(op(), budget);
|
|
30744
|
+
if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
|
|
30745
|
+
await persist({ ...CLOSED });
|
|
30746
|
+
}
|
|
30747
|
+
return { ok: true, value };
|
|
30748
|
+
} catch (err) {
|
|
30749
|
+
if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
|
|
30750
|
+
const reason = classifyFailure(err);
|
|
30751
|
+
const failures = current.consecutiveFailures + 1;
|
|
30752
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
30753
|
+
await persist({
|
|
30754
|
+
consecutiveFailures: failures,
|
|
30755
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
30756
|
+
lastFailure: reason
|
|
30757
|
+
});
|
|
30758
|
+
return { ok: false, reason };
|
|
30759
|
+
}
|
|
30760
|
+
}
|
|
30761
|
+
};
|
|
30762
|
+
}
|
|
30763
|
+
|
|
30764
|
+
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
30765
|
+
var ACTION_STRENGTH = {
|
|
30766
|
+
allow: 0,
|
|
30767
|
+
log: 1,
|
|
30768
|
+
warn: 2,
|
|
30769
|
+
redact: 3,
|
|
30770
|
+
block: 4
|
|
30771
|
+
};
|
|
30772
|
+
function ruleCategoryMap(wireRules, localRules) {
|
|
30773
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
30774
|
+
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
30775
|
+
for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
|
|
30776
|
+
for (const pack of bundledDetections()) {
|
|
30777
|
+
for (const rule of pack.rules) map2.set(rule.id, rule.category);
|
|
30778
|
+
}
|
|
30779
|
+
return map2;
|
|
30780
|
+
}
|
|
30781
|
+
function strongerOf(a, b) {
|
|
30782
|
+
if (a === null) return b;
|
|
30783
|
+
if (b === null) return a;
|
|
30784
|
+
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
30785
|
+
}
|
|
30786
|
+
function policyKey(policy) {
|
|
30787
|
+
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
30788
|
+
}
|
|
30789
|
+
function floorFor(policy, categoryByRuleId) {
|
|
30790
|
+
const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
|
|
30791
|
+
return category === void 0 ? null : DEFAULT_ACTIONS[category];
|
|
30792
|
+
}
|
|
30793
|
+
function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
30794
|
+
const merged = /* @__PURE__ */ new Map();
|
|
30795
|
+
const disabled = [];
|
|
30796
|
+
const remoteCategoryAction = /* @__PURE__ */ new Map();
|
|
30797
|
+
for (const policy of remotePolicies) {
|
|
30798
|
+
if (!policy.enabled) continue;
|
|
30799
|
+
if (!("category" in policy.target)) continue;
|
|
30800
|
+
if (remoteCategoryAction.has(policy.target.category)) continue;
|
|
30801
|
+
const floor = floorFor(policy, categoryByRuleId);
|
|
30802
|
+
remoteCategoryAction.set(
|
|
30803
|
+
policy.target.category,
|
|
30804
|
+
floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
|
|
30805
|
+
);
|
|
30806
|
+
}
|
|
30807
|
+
for (const policy of localPolicies) {
|
|
30808
|
+
if (!policy.enabled) {
|
|
30809
|
+
disabled.push(policy);
|
|
30810
|
+
continue;
|
|
30811
|
+
}
|
|
30812
|
+
const key = policyKey(policy);
|
|
30813
|
+
if (merged.has(key)) continue;
|
|
30814
|
+
let remoteFloor = null;
|
|
30815
|
+
if ("ruleId" in policy.target) {
|
|
30816
|
+
const category = categoryByRuleId.get(policy.target.ruleId);
|
|
30817
|
+
if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
|
|
30818
|
+
}
|
|
30819
|
+
merged.set(
|
|
30820
|
+
key,
|
|
30821
|
+
remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
|
|
30822
|
+
);
|
|
30823
|
+
}
|
|
30824
|
+
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
30825
|
+
for (const policy of merged.values()) {
|
|
30826
|
+
if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
|
|
30827
|
+
}
|
|
30828
|
+
for (const policy of remotePolicies) {
|
|
30829
|
+
if (!policy.enabled) {
|
|
30830
|
+
disabled.push(policy);
|
|
30831
|
+
continue;
|
|
30832
|
+
}
|
|
30833
|
+
const key = policyKey(policy);
|
|
30834
|
+
const floor = floorFor(policy, categoryByRuleId);
|
|
30835
|
+
let localFloor = null;
|
|
30836
|
+
if ("ruleId" in policy.target) {
|
|
30837
|
+
const category = categoryByRuleId.get(policy.target.ruleId);
|
|
30838
|
+
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
30839
|
+
}
|
|
30840
|
+
const effectiveFloor = strongerOf(floor, localFloor);
|
|
30841
|
+
const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
|
|
30842
|
+
const existing = merged.get(key);
|
|
30843
|
+
if (existing === void 0) {
|
|
30844
|
+
merged.set(key, clamped);
|
|
30845
|
+
continue;
|
|
30846
|
+
}
|
|
30847
|
+
if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
|
|
30848
|
+
merged.set(key, clamped);
|
|
30849
|
+
}
|
|
30850
|
+
}
|
|
30851
|
+
return [...merged.values(), ...disabled];
|
|
30852
|
+
}
|
|
30853
|
+
var AttachedDataGateway = class {
|
|
30854
|
+
constructor(deps) {
|
|
30855
|
+
this.deps = deps;
|
|
30856
|
+
}
|
|
30857
|
+
deps;
|
|
30858
|
+
/**
|
|
30859
|
+
* The control plane's OWN resolution of this session's inventory, captured by
|
|
30860
|
+
* ensureInventory. Null until the first successful forward — and it stays
|
|
30861
|
+
* null for the whole session when the control plane is unreachable, which is fine:
|
|
30862
|
+
* reKeyForForward then leaves the event's ids alone and the control plane resolves
|
|
30863
|
+
* what it can from the descriptors it already has.
|
|
30864
|
+
*/
|
|
30865
|
+
remoteInventory = null;
|
|
30866
|
+
// ---------------------------------------------------------------------
|
|
30867
|
+
// Writes: local first, then forward.
|
|
30868
|
+
// ---------------------------------------------------------------------
|
|
30869
|
+
async recordCapture(record2) {
|
|
30870
|
+
await this.deps.local.recordCapture(record2);
|
|
30871
|
+
await this.deps.forward.run(
|
|
30872
|
+
() => this.deps.client.ingestEvents({
|
|
30873
|
+
events: [record2.event],
|
|
30874
|
+
...record2.dedupe ? { dedupe: record2.dedupe } : {}
|
|
30875
|
+
}),
|
|
30876
|
+
{ decisionPath: true }
|
|
30877
|
+
);
|
|
30878
|
+
}
|
|
30879
|
+
async ensureInventory(ctx) {
|
|
30880
|
+
const resolved = await this.deps.local.ensureInventory(ctx);
|
|
30881
|
+
const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
|
|
30882
|
+
this.remoteInventory = remote.ok ? remote.value : null;
|
|
30883
|
+
const snapshot = await (async () => {
|
|
30884
|
+
try {
|
|
30885
|
+
return await this.deps.posture?.prepare() ?? null;
|
|
30886
|
+
} catch {
|
|
30887
|
+
return null;
|
|
30888
|
+
}
|
|
30889
|
+
})();
|
|
30890
|
+
if (snapshot) {
|
|
30891
|
+
try {
|
|
30892
|
+
await withTimeout(
|
|
30893
|
+
this.deps.posture?.send(snapshot) ?? Promise.resolve(),
|
|
30894
|
+
REQUEST_TIMEOUT_MS
|
|
30895
|
+
);
|
|
30896
|
+
} catch {
|
|
30897
|
+
}
|
|
30898
|
+
}
|
|
30899
|
+
return resolved;
|
|
30900
|
+
}
|
|
30901
|
+
// The id is minted CLIENT-side and stored verbatim: the control plane does NOT
|
|
30902
|
+
// re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
|
|
30903
|
+
// its own scoping columns, so the device and the forwarded copy
|
|
30904
|
+
// share one id space — which is what makes a re-post idempotent at all.
|
|
30905
|
+
//
|
|
30906
|
+
// Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
|
|
30907
|
+
// `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
|
|
30908
|
+
// what makes an attached retry safe: a capture-stubbed session row can still
|
|
30909
|
+
// be HEALED by the authoritative root, while a duplicate non-session event —
|
|
30910
|
+
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
30911
|
+
async recordAuditEvent(event) {
|
|
30912
|
+
await this.deps.local.recordAuditEvent(event);
|
|
30913
|
+
await this.deps.forward.run(
|
|
30914
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
30915
|
+
);
|
|
30916
|
+
}
|
|
30917
|
+
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
30918
|
+
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
30919
|
+
// client method yet) by pre-building the audit event from the natural key.
|
|
30920
|
+
// The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
|
|
30921
|
+
// which would write the event to the local store a second time.
|
|
30922
|
+
async recordLlmCall(input) {
|
|
30923
|
+
await this.deps.local.recordLlmCall(input);
|
|
30924
|
+
await this.deps.forward.run(
|
|
30925
|
+
() => this.deps.client.recordAuditEvent(
|
|
30926
|
+
reKeyForForward(llmAuditEvent(input), this.remoteInventory)
|
|
30927
|
+
)
|
|
30928
|
+
);
|
|
30929
|
+
}
|
|
30930
|
+
/**
|
|
30931
|
+
* Forward one batch, item by item, under ONE aggregate deadline.
|
|
30932
|
+
*
|
|
30933
|
+
* Per-item budgets bound each request and nothing bounded their sum — see
|
|
30934
|
+
* BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
|
|
30935
|
+
* rather than sent: the local write has already succeeded, so every caller
|
|
30936
|
+
* has a correct result to return, and a drop is the outcome this path is
|
|
30937
|
+
* built to accept (G8) where a blown hook timeout is not.
|
|
30938
|
+
*
|
|
30939
|
+
* Serial rather than concurrent on purpose. Firing N requests at once would
|
|
30940
|
+
* trade a latency problem for a burst the plane's own per-key rate limiting
|
|
30941
|
+
* would answer with the refusals the breaker then counts.
|
|
30942
|
+
*
|
|
30943
|
+
* WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
|
|
30944
|
+
* `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
|
|
30945
|
+
* lets status call the forward unhealthy; this path returns BEFORE `run` is
|
|
30946
|
+
* reached, so without the tally in `forward-drops.ts` a slow-but-answering
|
|
30947
|
+
* plane produces no failures, keeps the breaker closed, renders a healthy
|
|
30948
|
+
* block, and discards the tail of every batch indefinitely.
|
|
30949
|
+
*/
|
|
30950
|
+
async forwardBatch(inputs, toEvent) {
|
|
30951
|
+
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
30952
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
30953
|
+
const now = Date.now();
|
|
30954
|
+
if (now >= deadline) {
|
|
30955
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
30956
|
+
return;
|
|
30957
|
+
}
|
|
30958
|
+
const input = inputs[i];
|
|
30959
|
+
await this.deps.forward.run(
|
|
30960
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
|
|
30961
|
+
);
|
|
30962
|
+
}
|
|
30963
|
+
}
|
|
30964
|
+
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
30965
|
+
// gateway may write the whole batch in one local transaction, and looping
|
|
30966
|
+
// here would replace that with N separate local writes.
|
|
30967
|
+
async recordLlmCalls(inputs) {
|
|
30968
|
+
await this.deps.local.recordLlmCalls(inputs);
|
|
30969
|
+
await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
|
|
30970
|
+
}
|
|
30971
|
+
// `input.inspections` (secrets detected client-side in the tool's masked
|
|
30972
|
+
// target) ride along on the request's `inspections` field — the control plane
|
|
30973
|
+
// persists each as an inspection_findings row linked to this audit event
|
|
30974
|
+
// (see RecordAuditEventRequest in @akasecurity/schema). The masked
|
|
30975
|
+
// `target` already rides `input.attributes`, so no raw secret leaks either
|
|
30976
|
+
// way — this only stops the FINDING row itself from being dropped.
|
|
30977
|
+
async recordToolCalls(inputs) {
|
|
30978
|
+
await this.deps.local.recordToolCalls(inputs);
|
|
30979
|
+
await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
|
|
30980
|
+
}
|
|
30981
|
+
// Forwarded as a `config_scan` audit event: there is no dedicated
|
|
30982
|
+
// config-scan ingest endpoint, and the audit-event door is the one the
|
|
30983
|
+
// control plane already opens for client-minted, idempotent records.
|
|
30984
|
+
//
|
|
30985
|
+
// ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
|
|
30986
|
+
// re-derive the rest. A `ConfigScanRecord` is four things committed together
|
|
30987
|
+
// locally — the inventory `items`, this audit event, and the posture
|
|
30988
|
+
// `definitions`/`findings` that reference it — and three of them stay on the
|
|
30989
|
+
// device. Say that plainly rather than let the asymmetry with `recordCapture`
|
|
30990
|
+
// read as the same argument: there, findings are omitted BECAUSE the plane
|
|
30991
|
+
// re-derives them from `Event.content`; here there is no content to re-derive
|
|
30992
|
+
// from, so what is omitted is simply not sent.
|
|
30993
|
+
//
|
|
30994
|
+
// That is the wire contract as it stands rather than an oversight to patch
|
|
30995
|
+
// here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
|
|
30996
|
+
// is documented as tool-call findings — widening it to carry config-scan
|
|
30997
|
+
// findings is an egress change (a posture finding's `maskedMatch` holds the
|
|
30998
|
+
// matched command) and a decision about what an attached deployment is
|
|
30999
|
+
// entitled to, not a bug fix. An attached machine's config posture therefore
|
|
31000
|
+
// reaches the plane as the event only; the dashboard's own view of it is the
|
|
31001
|
+
// local store.
|
|
31002
|
+
async recordConfigScan(record2) {
|
|
31003
|
+
await this.deps.local.recordConfigScan(record2);
|
|
31004
|
+
await this.deps.forward.run(
|
|
31005
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
31006
|
+
);
|
|
31007
|
+
}
|
|
31008
|
+
async recordBlockedDetection(entry) {
|
|
31009
|
+
return this.deps.local.recordBlockedDetection(entry);
|
|
31010
|
+
}
|
|
31011
|
+
/**
|
|
31012
|
+
* LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
|
|
31013
|
+
* with no egress ingest endpoint, so there is nothing to forward to; adding a
|
|
31014
|
+
* forward here would be inventing a wire contract that does not exist. The
|
|
31015
|
+
* local write is the whole operation, and its summary is the real one — the
|
|
31016
|
+
* scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
|
|
31017
|
+
* returning the inner gateway's result keeps the retry semantics honest.
|
|
31018
|
+
*/
|
|
31019
|
+
async recordProjectEgress(input) {
|
|
31020
|
+
return this.deps.local.recordProjectEgress(input);
|
|
31021
|
+
}
|
|
31022
|
+
// ---------------------------------------------------------------------
|
|
31023
|
+
// Reads and device-local ledgers: pure delegation.
|
|
31024
|
+
// ---------------------------------------------------------------------
|
|
31025
|
+
async configInventoryReport() {
|
|
31026
|
+
return this.deps.local.configInventoryReport();
|
|
31027
|
+
}
|
|
31028
|
+
async readSessionProvider(sessionId) {
|
|
31029
|
+
return this.deps.local.readSessionProvider(sessionId);
|
|
31030
|
+
}
|
|
31031
|
+
async facets() {
|
|
31032
|
+
return this.deps.local.facets();
|
|
31033
|
+
}
|
|
31034
|
+
/**
|
|
31035
|
+
* Delegated UNMODIFIED — including its refusals.
|
|
31036
|
+
*
|
|
31037
|
+
* This is a fail-secure boundary: it decides whether an approved exception
|
|
31038
|
+
* lets a blocked action through. Under local-first the local store owns the
|
|
31039
|
+
* exception ledger, so the honest answer is whatever it says; wrapping this
|
|
31040
|
+
* in a fallback (`catch { return true }`, or defaulting on a timeout) would
|
|
31041
|
+
* turn a store error into a granted bypass. If the inner gateway rejects,
|
|
31042
|
+
* this rejects, and the runtime's own handling decides — which is asserted
|
|
31043
|
+
* end-to-end through runtime.capture rather than here.
|
|
31044
|
+
*/
|
|
31045
|
+
async consumeException(id) {
|
|
31046
|
+
return this.deps.local.consumeException(id);
|
|
31047
|
+
}
|
|
31048
|
+
async recentFindings(opts) {
|
|
31049
|
+
return this.deps.local.recentFindings(opts);
|
|
31050
|
+
}
|
|
31051
|
+
async healthSummary() {
|
|
31052
|
+
return this.deps.local.healthSummary();
|
|
31053
|
+
}
|
|
31054
|
+
async activityByDay(days) {
|
|
31055
|
+
return this.deps.local.activityByDay(days);
|
|
31056
|
+
}
|
|
31057
|
+
async tokenReports() {
|
|
31058
|
+
return this.deps.local.tokenReports();
|
|
31059
|
+
}
|
|
31060
|
+
async knownContentHashes() {
|
|
31061
|
+
return this.deps.local.knownContentHashes();
|
|
31062
|
+
}
|
|
31063
|
+
async scanLedger(rulesetHash) {
|
|
31064
|
+
return this.deps.local.scanLedger(rulesetHash);
|
|
31065
|
+
}
|
|
31066
|
+
async recordScanned(entries) {
|
|
31067
|
+
return this.deps.local.recordScanned(entries);
|
|
31068
|
+
}
|
|
31069
|
+
async getRuleProbeVerdict(ruleKey) {
|
|
31070
|
+
return this.deps.local.getRuleProbeVerdict(ruleKey);
|
|
31071
|
+
}
|
|
31072
|
+
async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs) {
|
|
31073
|
+
return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs);
|
|
31074
|
+
}
|
|
31075
|
+
async openAtRestKeysForPath(path) {
|
|
31076
|
+
return this.deps.local.openAtRestKeysForPath(path);
|
|
31077
|
+
}
|
|
31078
|
+
async resolvedAtRestKeysForPath(path) {
|
|
31079
|
+
return this.deps.local.resolvedAtRestKeysForPath(path);
|
|
31080
|
+
}
|
|
31081
|
+
async insertResolution(input) {
|
|
31082
|
+
return this.deps.local.insertResolution(input);
|
|
31083
|
+
}
|
|
31084
|
+
async close() {
|
|
31085
|
+
return this.deps.local.close();
|
|
31086
|
+
}
|
|
31087
|
+
// ---------------------------------------------------------------------
|
|
31088
|
+
// Policy
|
|
31089
|
+
// ---------------------------------------------------------------------
|
|
31090
|
+
async getPolicyBundle() {
|
|
31091
|
+
const local = await this.deps.local.getPolicyBundle();
|
|
31092
|
+
const cached2 = await (async () => {
|
|
31093
|
+
try {
|
|
31094
|
+
return await this.deps.readCachedBundle();
|
|
31095
|
+
} catch {
|
|
31096
|
+
return null;
|
|
31097
|
+
}
|
|
31098
|
+
})();
|
|
31099
|
+
if (cached2 === null) return local;
|
|
31100
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
31101
|
+
for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
|
|
31102
|
+
if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
|
|
31103
|
+
}
|
|
31104
|
+
const rules = [...byRuleId.values()];
|
|
31105
|
+
return {
|
|
31106
|
+
...local,
|
|
31107
|
+
// The remote version identifies the composed bundle for the poller.
|
|
31108
|
+
version: cached2.version,
|
|
31109
|
+
rules,
|
|
31110
|
+
policies: mergeRaiseOnly(
|
|
31111
|
+
local.policies,
|
|
31112
|
+
cached2.policies,
|
|
31113
|
+
ruleCategoryMap(cached2.rules, local.rules)
|
|
31114
|
+
),
|
|
31115
|
+
customKeywords: [...local.customKeywords, ...cached2.customKeywords]
|
|
31116
|
+
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
31117
|
+
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
31118
|
+
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
31119
|
+
// anything able to write policy-cache.json, a kill-switch over the
|
|
31120
|
+
// compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
|
|
31121
|
+
// zero local detection. Spread from `local` above, and deliberately not
|
|
31122
|
+
// re-read from `cached` here.
|
|
31123
|
+
//
|
|
31124
|
+
// THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
|
|
31125
|
+
// and each named here so a reader can tell a decision from an omission:
|
|
31126
|
+
//
|
|
31127
|
+
// `exceptions` — an exception SUPPRESSES a detection, so honoring
|
|
31128
|
+
// one from an unsigned on-disk cache would let
|
|
31129
|
+
// anything able to write that file turn rules off.
|
|
31130
|
+
// Every other field this merge accepts can only
|
|
31131
|
+
// RAISE enforcement; this is the one that cannot,
|
|
31132
|
+
// so it stays local-only until the bundle is
|
|
31133
|
+
// signed. Exceptions remain a device-local ledger.
|
|
31134
|
+
// `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
|
|
31135
|
+
// recoverable, which is a CUSTODY change: it puts
|
|
31136
|
+
// the detected value in the local vault instead of
|
|
31137
|
+
// destroying it. Taking that instruction from the
|
|
31138
|
+
// cache would let a remote party turn one-way
|
|
31139
|
+
// redaction into retention. Dropping it keeps the
|
|
31140
|
+
// one-way behaviour, which the schema itself calls
|
|
31141
|
+
// "the safe direction to default".
|
|
31142
|
+
// `ruleVersions` — remote rules fall back to their own spec version.
|
|
31143
|
+
// Cosmetic rather than protective: it only affects
|
|
31144
|
+
// how a finding is version-attributed, and the two
|
|
31145
|
+
// sides may therefore attribute org rules
|
|
31146
|
+
// differently. Worth carrying once there is a
|
|
31147
|
+
// reader that needs it; nothing reads it today.
|
|
31148
|
+
};
|
|
31149
|
+
}
|
|
31150
|
+
// ---------------------------------------------------------------------
|
|
31151
|
+
// LocalStoreMaintenance — by delegation (D3).
|
|
31152
|
+
//
|
|
31153
|
+
// Implementing these is what actually closes the skipped-local-maintenance
|
|
31154
|
+
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
31155
|
+
// any object carrying all five, so the composite qualifies and SessionStart
|
|
31156
|
+
// runs maintenance on the device's real store.
|
|
31157
|
+
//
|
|
31158
|
+
// ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
31159
|
+
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
31160
|
+
// return value directly; declaring them `async` here would hand those call
|
|
31161
|
+
// sites a Promise and silently break both.
|
|
31162
|
+
// ---------------------------------------------------------------------
|
|
31163
|
+
async sweepTerminalExceptions(retentionMs) {
|
|
31164
|
+
return this.deps.local.sweepTerminalExceptions(retentionMs);
|
|
31165
|
+
}
|
|
31166
|
+
capWarnEraEnforcement(policyMode) {
|
|
31167
|
+
return this.deps.local.capWarnEraEnforcement(policyMode);
|
|
31168
|
+
}
|
|
31169
|
+
async recordProjectFiles(projectId, scan2) {
|
|
31170
|
+
return this.deps.local.recordProjectFiles(projectId, scan2);
|
|
31171
|
+
}
|
|
31172
|
+
async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
|
|
31173
|
+
return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
|
|
31174
|
+
}
|
|
31175
|
+
staleBinaryNotice(currentVersion) {
|
|
31176
|
+
return this.deps.local.staleBinaryNotice(currentVersion);
|
|
31177
|
+
}
|
|
31178
|
+
};
|
|
31179
|
+
function reKeyForForward(event, remote) {
|
|
31180
|
+
if (remote === null) {
|
|
31181
|
+
const stripped = { ...event };
|
|
31182
|
+
delete stripped.hostId;
|
|
31183
|
+
delete stripped.harnessId;
|
|
31184
|
+
delete stripped.sourceProjectId;
|
|
31185
|
+
return stripped;
|
|
31186
|
+
}
|
|
31187
|
+
const rekeyed = { ...event };
|
|
31188
|
+
delete rekeyed.hostId;
|
|
31189
|
+
delete rekeyed.harnessId;
|
|
31190
|
+
delete rekeyed.sourceProjectId;
|
|
31191
|
+
if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
|
|
31192
|
+
if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
|
|
31193
|
+
if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
|
|
31194
|
+
return rekeyed;
|
|
31195
|
+
}
|
|
31196
|
+
var BATCH_FORWARD_BUDGET_MS = 3e3;
|
|
31197
|
+
function llmAuditEvent(input) {
|
|
31198
|
+
return {
|
|
31199
|
+
id: llmCallId(input.sessionId, input.messageId),
|
|
31200
|
+
eventType: "llm_call",
|
|
31201
|
+
startedAt: input.startedAt,
|
|
31202
|
+
parentId: input.parentId,
|
|
31203
|
+
rootSessionId: input.rootSessionId,
|
|
31204
|
+
attributes: input.attributes
|
|
31205
|
+
};
|
|
31206
|
+
}
|
|
31207
|
+
function toolAuditEvent(input) {
|
|
31208
|
+
return {
|
|
31209
|
+
id: toolCallId(input.sessionId, input.toolUseId),
|
|
31210
|
+
eventType: "tool_call",
|
|
31211
|
+
startedAt: input.startedAt,
|
|
31212
|
+
parentId: input.parentId,
|
|
31213
|
+
rootSessionId: input.rootSessionId,
|
|
31214
|
+
attributes: input.attributes,
|
|
31215
|
+
inspections: input.inspections
|
|
31216
|
+
};
|
|
31217
|
+
}
|
|
31218
|
+
|
|
31219
|
+
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
31220
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
31221
|
+
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
31222
|
+
import { join as join19 } from "path";
|
|
31223
|
+
|
|
31224
|
+
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
31225
|
+
import { rename as rename2 } from "fs/promises";
|
|
31226
|
+
var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
31227
|
+
var ATTEMPTS = 5;
|
|
31228
|
+
var delay = (ms) => new Promise((resolve3) => {
|
|
31229
|
+
setTimeout(resolve3, ms);
|
|
31230
|
+
});
|
|
31231
|
+
async function publishByRename(tmp, file2, move = rename2) {
|
|
31232
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
31233
|
+
try {
|
|
31234
|
+
await move(tmp, file2);
|
|
31235
|
+
return;
|
|
31236
|
+
} catch (err) {
|
|
31237
|
+
const code = err.code;
|
|
31238
|
+
if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
|
|
31239
|
+
await delay(attempt * 10);
|
|
31240
|
+
}
|
|
31241
|
+
}
|
|
31242
|
+
}
|
|
31243
|
+
|
|
31244
|
+
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
31245
|
+
function createPolicyStore(dir = dataDir()) {
|
|
31246
|
+
const file2 = join19(dir, "policy-cache.json");
|
|
31247
|
+
async function read() {
|
|
31248
|
+
try {
|
|
31249
|
+
const raw = await readFile2(file2, "utf8");
|
|
31250
|
+
const parsed2 = JSON.parse(raw);
|
|
31251
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
31252
|
+
const record2 = parsed2;
|
|
31253
|
+
const bundle = PolicyBundle.parse(record2.bundle);
|
|
31254
|
+
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
31255
|
+
const etag = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
31256
|
+
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
31257
|
+
} catch {
|
|
31258
|
+
return null;
|
|
31259
|
+
}
|
|
31260
|
+
}
|
|
31261
|
+
async function write(bundle, etag) {
|
|
31262
|
+
await ensureDataDir(dir);
|
|
31263
|
+
const stored = {
|
|
31264
|
+
bundle,
|
|
31265
|
+
fetchedAtMs: Date.now(),
|
|
31266
|
+
...etag === void 0 ? {} : { etag }
|
|
31267
|
+
};
|
|
31268
|
+
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
31269
|
+
try {
|
|
31270
|
+
await writeFile2(tmp, JSON.stringify(stored), {
|
|
31271
|
+
encoding: "utf8",
|
|
31272
|
+
mode: DATA_FILE_MODE,
|
|
31273
|
+
flag: "wx"
|
|
31274
|
+
});
|
|
31275
|
+
await publishByRename(tmp, file2);
|
|
31276
|
+
} catch (err) {
|
|
31277
|
+
await rm(tmp, { force: true }).catch(() => void 0);
|
|
31278
|
+
throw err;
|
|
31279
|
+
}
|
|
31280
|
+
}
|
|
31281
|
+
return { read, write, file: file2 };
|
|
31282
|
+
}
|
|
31283
|
+
|
|
31284
|
+
// ../../packages/remote/src/http.ts
|
|
31285
|
+
import { request as httpRequest } from "http";
|
|
31286
|
+
import { request as httpsRequest } from "https";
|
|
31287
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
31288
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
31289
|
+
var RemoteRequestError = class extends Error {
|
|
31290
|
+
constructor(status) {
|
|
31291
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
31292
|
+
this.status = status;
|
|
31293
|
+
this.name = "RemoteRequestError";
|
|
31294
|
+
}
|
|
31295
|
+
status;
|
|
31296
|
+
};
|
|
31297
|
+
var RemoteRequestInvalid = class extends Error {
|
|
31298
|
+
constructor(route, cause) {
|
|
31299
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
31300
|
+
this.cause = cause;
|
|
31301
|
+
this.name = "RemoteRequestInvalid";
|
|
31302
|
+
}
|
|
31303
|
+
cause;
|
|
31304
|
+
};
|
|
31305
|
+
var RemoteResponseInvalid = class extends Error {
|
|
31306
|
+
constructor(route, detail) {
|
|
31307
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
31308
|
+
this.name = "RemoteResponseInvalid";
|
|
31309
|
+
}
|
|
31310
|
+
};
|
|
31311
|
+
var RemoteTransportError = class extends Error {
|
|
31312
|
+
/**
|
|
31313
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
31314
|
+
* refused.
|
|
31315
|
+
*
|
|
31316
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
31317
|
+
* all. It exists because two paths reject after a status has already been
|
|
31318
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
31319
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
31320
|
+
* outage, which sends the reader to look at their network instead of their
|
|
31321
|
+
* credential.
|
|
31322
|
+
*/
|
|
31323
|
+
constructor(reason, status) {
|
|
31324
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
31325
|
+
this.status = status;
|
|
31326
|
+
this.name = "RemoteTransportError";
|
|
31327
|
+
}
|
|
31328
|
+
status;
|
|
31329
|
+
};
|
|
31330
|
+
async function send(options) {
|
|
31331
|
+
const url2 = new URL(options.url);
|
|
31332
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
31333
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
31334
|
+
const requestOptions = {
|
|
31335
|
+
method: options.method,
|
|
31336
|
+
headers: {
|
|
31337
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
31338
|
+
// last they win, and two of the values below are ones no caller may
|
|
31339
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
31340
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
31341
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
31342
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
31343
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
31344
|
+
// conditional GET — is untouched by this order.
|
|
31345
|
+
...options.headers,
|
|
31346
|
+
// The credential. One header, matching what the deployment authenticates
|
|
31347
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
31348
|
+
// it can be logged by an intermediary for no gain.
|
|
31349
|
+
"x-api-key": options.apiKey,
|
|
31350
|
+
accept: "application/json",
|
|
31351
|
+
...options.body === void 0 ? {} : {
|
|
31352
|
+
"content-type": "application/json",
|
|
31353
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
31354
|
+
// character count is truncated by the receiver.
|
|
31355
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
31356
|
+
}
|
|
31357
|
+
}
|
|
31358
|
+
};
|
|
31359
|
+
return new Promise((resolve3, reject) => {
|
|
31360
|
+
let settled = false;
|
|
31361
|
+
const fail = (reason, status) => {
|
|
31362
|
+
if (settled) return;
|
|
31363
|
+
settled = true;
|
|
31364
|
+
reject(new RemoteTransportError(reason, status));
|
|
31365
|
+
};
|
|
31366
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
31367
|
+
const chunks = [];
|
|
31368
|
+
let size = 0;
|
|
31369
|
+
res.on("data", (chunk) => {
|
|
31370
|
+
size += chunk.length;
|
|
31371
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
31372
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
31373
|
+
res.destroy();
|
|
31374
|
+
req.destroy();
|
|
31375
|
+
return;
|
|
31376
|
+
}
|
|
31377
|
+
chunks.push(chunk);
|
|
31378
|
+
});
|
|
31379
|
+
res.on("aborted", () => {
|
|
31380
|
+
fail("the response was aborted", res.statusCode);
|
|
31381
|
+
});
|
|
31382
|
+
res.on("end", () => {
|
|
31383
|
+
if (settled) return;
|
|
31384
|
+
settled = true;
|
|
31385
|
+
resolve3({
|
|
31386
|
+
status: res.statusCode ?? 0,
|
|
31387
|
+
headers: res.headers,
|
|
31388
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
31389
|
+
});
|
|
31390
|
+
});
|
|
31391
|
+
});
|
|
31392
|
+
const deadline = setTimeout(() => {
|
|
31393
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
31394
|
+
req.destroy();
|
|
31395
|
+
}, timeoutMs);
|
|
31396
|
+
deadline.unref();
|
|
31397
|
+
req.on("upgrade", (_res, socket) => {
|
|
31398
|
+
fail("the deployment answered with a protocol upgrade");
|
|
31399
|
+
socket.destroy();
|
|
31400
|
+
});
|
|
31401
|
+
req.on("close", () => {
|
|
31402
|
+
fail("the connection closed before a response was read");
|
|
31403
|
+
clearTimeout(deadline);
|
|
31404
|
+
});
|
|
31405
|
+
req.on("error", (err) => {
|
|
31406
|
+
fail(err.message);
|
|
31407
|
+
});
|
|
31408
|
+
if (options.body !== void 0) req.write(options.body);
|
|
31409
|
+
req.end();
|
|
31410
|
+
});
|
|
31411
|
+
}
|
|
31412
|
+
|
|
31413
|
+
// ../../packages/remote/src/client.ts
|
|
31414
|
+
var ROUTES = {
|
|
31415
|
+
events: "/v1/events",
|
|
31416
|
+
auditEvents: "/v1/audit-events",
|
|
31417
|
+
inventory: "/v1/inventory",
|
|
31418
|
+
storePosture: "/v1/store-posture",
|
|
31419
|
+
policyBundle: "/v1/policy-bundle",
|
|
31420
|
+
whoami: "/v1/plugin/whoami"
|
|
31421
|
+
};
|
|
31422
|
+
function headerValue(response, name) {
|
|
31423
|
+
const raw = response.headers[name];
|
|
31424
|
+
if (raw === void 0) return void 0;
|
|
31425
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
31426
|
+
}
|
|
31427
|
+
function okBody(response) {
|
|
31428
|
+
if (response.status < 200 || response.status >= 300) {
|
|
31429
|
+
throw new RemoteRequestError(response.status);
|
|
31430
|
+
}
|
|
31431
|
+
return response.body;
|
|
31432
|
+
}
|
|
31433
|
+
function parsed(schema, body, route) {
|
|
31434
|
+
let json2;
|
|
31435
|
+
try {
|
|
31436
|
+
json2 = JSON.parse(body);
|
|
31437
|
+
} catch {
|
|
31438
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
31439
|
+
}
|
|
31440
|
+
const result = schema.safeParse(json2);
|
|
31441
|
+
if (!result.success) {
|
|
31442
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
31443
|
+
}
|
|
31444
|
+
return result.data;
|
|
31445
|
+
}
|
|
31446
|
+
function withoutTrailingSlashes(endpoint) {
|
|
31447
|
+
let end = endpoint.length;
|
|
31448
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
31449
|
+
return endpoint.slice(0, end);
|
|
31450
|
+
}
|
|
31451
|
+
var SLASH = "/".charCodeAt(0);
|
|
31452
|
+
function createRemoteClient(options) {
|
|
31453
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
31454
|
+
const url2 = (route) => `${base}${route}`;
|
|
31455
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
31456
|
+
return {
|
|
31457
|
+
async ingestEvents(batch) {
|
|
31458
|
+
const response = await send({
|
|
31459
|
+
...common,
|
|
31460
|
+
method: "POST",
|
|
31461
|
+
url: url2(ROUTES.events),
|
|
31462
|
+
body: JSON.stringify(batch)
|
|
31463
|
+
});
|
|
31464
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
31465
|
+
},
|
|
31466
|
+
async ingestInventory(context) {
|
|
31467
|
+
const response = await send({
|
|
31468
|
+
...common,
|
|
31469
|
+
method: "POST",
|
|
31470
|
+
url: url2(ROUTES.inventory),
|
|
31471
|
+
body: JSON.stringify(context)
|
|
31472
|
+
});
|
|
31473
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
31474
|
+
},
|
|
31475
|
+
async recordAuditEvent(event) {
|
|
31476
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
31477
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
31478
|
+
const submission = validated.data;
|
|
31479
|
+
const response = await send({
|
|
31480
|
+
...common,
|
|
31481
|
+
method: "POST",
|
|
31482
|
+
url: url2(ROUTES.auditEvents),
|
|
31483
|
+
body: JSON.stringify(submission)
|
|
31484
|
+
});
|
|
31485
|
+
okBody(response);
|
|
31486
|
+
},
|
|
31487
|
+
async reportStorePosture(snapshot) {
|
|
31488
|
+
const response = await send({
|
|
31489
|
+
...common,
|
|
31490
|
+
method: "POST",
|
|
31491
|
+
url: url2(ROUTES.storePosture),
|
|
31492
|
+
body: JSON.stringify(snapshot)
|
|
31493
|
+
});
|
|
31494
|
+
okBody(response);
|
|
31495
|
+
},
|
|
31496
|
+
async getPolicyBundle(etag) {
|
|
31497
|
+
const response = await send({
|
|
31498
|
+
...common,
|
|
31499
|
+
method: "GET",
|
|
31500
|
+
url: url2(ROUTES.policyBundle),
|
|
31501
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
31502
|
+
});
|
|
31503
|
+
if (response.status === 304) {
|
|
31504
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
31505
|
+
}
|
|
31506
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
31507
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
31508
|
+
},
|
|
31509
|
+
async whoami() {
|
|
31510
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
31511
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
31512
|
+
}
|
|
31513
|
+
};
|
|
31514
|
+
}
|
|
31515
|
+
|
|
31516
|
+
// ../../packages/plugin-runtime/src/attached/posture-reporter.ts
|
|
31517
|
+
var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
31518
|
+
function createPostureReporter(deps) {
|
|
31519
|
+
async function prepare() {
|
|
31520
|
+
try {
|
|
31521
|
+
const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
|
|
31522
|
+
if (state === null) return null;
|
|
31523
|
+
const nowMs = deps.now();
|
|
31524
|
+
const elapsed = nowMs - state.lastAttemptedAtMs;
|
|
31525
|
+
if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
|
|
31526
|
+
try {
|
|
31527
|
+
await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
|
|
31528
|
+
} catch {
|
|
31529
|
+
}
|
|
31530
|
+
const { readError, ...measurement } = deps.readStore();
|
|
31531
|
+
if (readError) return null;
|
|
31532
|
+
let plugin;
|
|
31533
|
+
try {
|
|
31534
|
+
plugin = await deps.pluginBlock?.();
|
|
31535
|
+
} catch {
|
|
31536
|
+
plugin = void 0;
|
|
31537
|
+
}
|
|
31538
|
+
return {
|
|
31539
|
+
deviceId: state.deviceId,
|
|
31540
|
+
hostname: deps.hostname(),
|
|
31541
|
+
capturedAt: nowMs,
|
|
31542
|
+
...measurement,
|
|
31543
|
+
// Omit the key rather than spread an explicit `undefined` —
|
|
31544
|
+
// exactOptionalPropertyTypes distinguishes the two, and the bridge in
|
|
31545
|
+
// factory.ts keys on presence.
|
|
31546
|
+
...plugin === void 0 ? {} : { plugin }
|
|
31547
|
+
};
|
|
31548
|
+
} catch {
|
|
31549
|
+
return null;
|
|
31550
|
+
}
|
|
31551
|
+
}
|
|
31552
|
+
async function send2(snapshot) {
|
|
31553
|
+
try {
|
|
31554
|
+
await deps.report(snapshot);
|
|
31555
|
+
} catch {
|
|
31556
|
+
}
|
|
31557
|
+
}
|
|
31558
|
+
return { prepare, send: send2 };
|
|
31559
|
+
}
|
|
31560
|
+
|
|
31561
|
+
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
31562
|
+
import { statSync as statSync9 } from "fs";
|
|
31563
|
+
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31564
|
+
|
|
31565
|
+
// ../../packages/plugin-runtime/src/attached/action-counts.ts
|
|
31566
|
+
function emptyActionCounts() {
|
|
31567
|
+
return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
|
|
31568
|
+
}
|
|
31569
|
+
function isActionTaken(value) {
|
|
31570
|
+
return ACTION_TAKEN_KEYS.includes(value);
|
|
31571
|
+
}
|
|
31572
|
+
|
|
31573
|
+
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
31574
|
+
var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
|
|
31575
|
+
function isSchemaAbsent(err) {
|
|
31576
|
+
return err instanceof Error && /no such table/i.test(err.message);
|
|
31577
|
+
}
|
|
31578
|
+
function emptyReadout(readError = false) {
|
|
31579
|
+
const byAction = emptyActionCounts();
|
|
31580
|
+
return {
|
|
31581
|
+
storePresent: false,
|
|
31582
|
+
schemaVersion: null,
|
|
31583
|
+
findingsTotal: 0,
|
|
31584
|
+
findingsFirstAt: null,
|
|
31585
|
+
findingsLastAt: null,
|
|
31586
|
+
packs: [],
|
|
31587
|
+
policyCounts: { total: 0, disabled: 0, byAction },
|
|
31588
|
+
readError
|
|
31589
|
+
};
|
|
31590
|
+
}
|
|
31591
|
+
function readStorePosture(dbPath2) {
|
|
31592
|
+
try {
|
|
31593
|
+
statSync9(dbPath2);
|
|
31594
|
+
} catch (err) {
|
|
31595
|
+
const code = err.code;
|
|
31596
|
+
if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
|
|
31597
|
+
return emptyReadout(true);
|
|
31598
|
+
}
|
|
31599
|
+
let db = null;
|
|
31600
|
+
let version2 = null;
|
|
31601
|
+
let packs2 = [];
|
|
31602
|
+
let policyCounts = {
|
|
31603
|
+
total: 0,
|
|
31604
|
+
disabled: 0,
|
|
31605
|
+
byAction: emptyActionCounts()
|
|
31606
|
+
};
|
|
31607
|
+
let findingsTotal = 0;
|
|
31608
|
+
let findingsFirstAt = null;
|
|
31609
|
+
let findingsLastAt = null;
|
|
31610
|
+
const currentReadout = () => ({
|
|
31611
|
+
storePresent: true,
|
|
31612
|
+
schemaVersion: version2,
|
|
31613
|
+
findingsTotal,
|
|
31614
|
+
findingsFirstAt,
|
|
31615
|
+
findingsLastAt,
|
|
31616
|
+
packs: packs2,
|
|
31617
|
+
policyCounts,
|
|
31618
|
+
readError: false
|
|
31619
|
+
});
|
|
31620
|
+
try {
|
|
31621
|
+
db = new DatabaseSync3(dbPath2, { readOnly: true });
|
|
31622
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
31623
|
+
version2 = db.prepare("PRAGMA user_version").get().user_version;
|
|
31624
|
+
try {
|
|
31625
|
+
const packRows = db.prepare(
|
|
31626
|
+
`SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
|
|
31627
|
+
).all();
|
|
31628
|
+
packs2 = packRows.map((r) => ({
|
|
31629
|
+
packId: `${r.namespace}/${r.pack_id}`,
|
|
31630
|
+
version: r.version,
|
|
31631
|
+
enabled: r.enabled !== 0,
|
|
31632
|
+
updatedAt: r.updated_at == null ? null : String(r.updated_at)
|
|
31633
|
+
}));
|
|
31634
|
+
} catch (err) {
|
|
31635
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
31636
|
+
}
|
|
31637
|
+
try {
|
|
31638
|
+
const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
|
|
31639
|
+
const byAction = emptyActionCounts();
|
|
31640
|
+
let disabled = 0;
|
|
31641
|
+
for (const row of policyRows) {
|
|
31642
|
+
if (row.enabled === 0) disabled += 1;
|
|
31643
|
+
if (isActionTaken(row.action)) byAction[row.action] += 1;
|
|
31644
|
+
}
|
|
31645
|
+
policyCounts = { total: policyRows.length, disabled, byAction };
|
|
31646
|
+
} catch (err) {
|
|
31647
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
31648
|
+
}
|
|
31649
|
+
try {
|
|
31650
|
+
const agg = db.prepare(
|
|
31651
|
+
`SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
|
|
31652
|
+
FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
|
|
31653
|
+
WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
|
|
31654
|
+
).get();
|
|
31655
|
+
findingsTotal = agg.n;
|
|
31656
|
+
findingsFirstAt = agg.firstAt;
|
|
31657
|
+
findingsLastAt = agg.lastAt;
|
|
31658
|
+
} catch (err) {
|
|
31659
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
31660
|
+
}
|
|
31661
|
+
return currentReadout();
|
|
31662
|
+
} catch {
|
|
31663
|
+
return emptyReadout(true);
|
|
31664
|
+
} finally {
|
|
31665
|
+
try {
|
|
31666
|
+
db?.close();
|
|
31667
|
+
} catch {
|
|
31668
|
+
}
|
|
31669
|
+
}
|
|
31670
|
+
}
|
|
31671
|
+
|
|
31672
|
+
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
31673
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
31674
|
+
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
31675
|
+
import { join as join20 } from "path";
|
|
31676
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
31677
|
+
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
31678
|
+
const file2 = join20(dir, "posture-state.json");
|
|
31679
|
+
const legacyFile = legacyDir === void 0 ? null : join20(legacyDir, "posture-state.json");
|
|
31680
|
+
async function persist(state) {
|
|
31681
|
+
await ensureDataDir(dir);
|
|
31682
|
+
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
31683
|
+
try {
|
|
31684
|
+
await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
31685
|
+
await publishByRename(tmp, file2);
|
|
31686
|
+
} catch (err) {
|
|
31687
|
+
await rm2(tmp, { force: true }).catch(() => void 0);
|
|
31688
|
+
throw err;
|
|
31689
|
+
}
|
|
31690
|
+
}
|
|
31691
|
+
async function readFrom(path) {
|
|
31692
|
+
let raw;
|
|
31693
|
+
try {
|
|
31694
|
+
raw = await readFile3(path, "utf8");
|
|
31695
|
+
} catch (err) {
|
|
31696
|
+
const code = err.code;
|
|
31697
|
+
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
31698
|
+
throw err;
|
|
31699
|
+
}
|
|
31700
|
+
try {
|
|
31701
|
+
const parsed2 = JSON.parse(raw);
|
|
31702
|
+
if (typeof parsed2 === "object" && parsed2 !== null) {
|
|
31703
|
+
const record2 = parsed2;
|
|
31704
|
+
if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
|
|
31705
|
+
const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
|
|
31706
|
+
return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
|
|
31707
|
+
}
|
|
31708
|
+
}
|
|
31709
|
+
} catch {
|
|
31710
|
+
}
|
|
31711
|
+
return null;
|
|
31712
|
+
}
|
|
31713
|
+
async function read() {
|
|
31714
|
+
const current = await readFrom(file2);
|
|
31715
|
+
if (current) return current;
|
|
31716
|
+
const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
|
|
31717
|
+
if (legacy) {
|
|
31718
|
+
try {
|
|
31719
|
+
await persist(legacy);
|
|
31720
|
+
} catch {
|
|
31721
|
+
}
|
|
31722
|
+
return legacy;
|
|
31723
|
+
}
|
|
31724
|
+
const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
|
|
31725
|
+
try {
|
|
31726
|
+
await ensureDataDir(dir);
|
|
31727
|
+
if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
|
|
31728
|
+
} catch {
|
|
31729
|
+
return null;
|
|
31730
|
+
}
|
|
31731
|
+
const winner = await readFrom(file2).catch(() => null);
|
|
31732
|
+
if (winner) return winner;
|
|
31733
|
+
try {
|
|
31734
|
+
await persist(fresh);
|
|
31735
|
+
} catch {
|
|
31736
|
+
return null;
|
|
31737
|
+
}
|
|
31738
|
+
return fresh;
|
|
31739
|
+
}
|
|
31740
|
+
async function markAttempted(deviceId, atMs) {
|
|
31741
|
+
await persist({ deviceId, lastAttemptedAtMs: atMs });
|
|
31742
|
+
}
|
|
31743
|
+
return { read, markAttempted, file: file2 };
|
|
31744
|
+
}
|
|
31745
|
+
|
|
31746
|
+
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
31747
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
31748
|
+
import { join as join21 } from "path";
|
|
31749
|
+
|
|
31750
|
+
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
31751
|
+
var REFUSAL_LINES = {
|
|
31752
|
+
unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
|
|
31753
|
+
forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
|
|
31754
|
+
};
|
|
31755
|
+
var OUTCOME_LINES = {
|
|
31756
|
+
ok: "policy synced",
|
|
31757
|
+
"not-modified": "policy up to date",
|
|
31758
|
+
unauthorized: REFUSAL_LINES.unauthorized,
|
|
31759
|
+
forbidden: REFUSAL_LINES.forbidden,
|
|
31760
|
+
unreachable: "control plane unreachable at last attempt",
|
|
31761
|
+
"invalid-bundle": "control plane sent a policy bundle this build cannot read"
|
|
31762
|
+
};
|
|
31763
|
+
|
|
31764
|
+
// ../../packages/plugin-runtime/src/attached/sync-trigger.ts
|
|
31765
|
+
import { spawn } from "child_process";
|
|
31766
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
31767
|
+
var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
|
|
31768
|
+
|
|
31769
|
+
// ../../packages/plugin-runtime/src/attached/factory.ts
|
|
31770
|
+
import { hostname as hostname5 } from "os";
|
|
31771
|
+
|
|
31772
|
+
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
31773
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
30359
31774
|
|
|
30360
31775
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
30361
31776
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -30396,12 +31811,12 @@ var StandaloneDataGateway = class {
|
|
|
30396
31811
|
// reconciler drops the whole pass and recovers it idempotently on the next read.
|
|
30397
31812
|
recordLlmCalls(inputs) {
|
|
30398
31813
|
if (inputs.length === 0) return Promise.resolve();
|
|
30399
|
-
return new Promise((
|
|
31814
|
+
return new Promise((resolve3, reject) => {
|
|
30400
31815
|
try {
|
|
30401
31816
|
this.db.auditEvents.runInTransaction(() => {
|
|
30402
31817
|
for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
|
|
30403
31818
|
});
|
|
30404
|
-
|
|
31819
|
+
resolve3();
|
|
30405
31820
|
} catch (err) {
|
|
30406
31821
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
30407
31822
|
}
|
|
@@ -30413,12 +31828,12 @@ var StandaloneDataGateway = class {
|
|
|
30413
31828
|
// drops the whole pass and recovers it idempotently next time.
|
|
30414
31829
|
recordToolCalls(inputs) {
|
|
30415
31830
|
if (inputs.length === 0) return Promise.resolve();
|
|
30416
|
-
return new Promise((
|
|
31831
|
+
return new Promise((resolve3, reject) => {
|
|
30417
31832
|
try {
|
|
30418
31833
|
this.db.auditEvents.runInTransaction(() => {
|
|
30419
31834
|
for (const input of inputs) this.writeToolCall(input);
|
|
30420
31835
|
});
|
|
30421
|
-
|
|
31836
|
+
resolve3();
|
|
30422
31837
|
} catch (err) {
|
|
30423
31838
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
30424
31839
|
}
|
|
@@ -30560,7 +31975,7 @@ var StandaloneDataGateway = class {
|
|
|
30560
31975
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
30561
31976
|
const installed = this.installedScanRules();
|
|
30562
31977
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
30563
|
-
id:
|
|
31978
|
+
id: randomUUID18(),
|
|
30564
31979
|
scope: "global",
|
|
30565
31980
|
target: { ruleId },
|
|
30566
31981
|
action,
|
|
@@ -30714,27 +32129,73 @@ var StandaloneDataGateway = class {
|
|
|
30714
32129
|
}
|
|
30715
32130
|
};
|
|
30716
32131
|
|
|
32132
|
+
// ../../packages/plugin-runtime/src/attached/factory.ts
|
|
32133
|
+
function resolveGatewayForConfig(config2, meta3) {
|
|
32134
|
+
const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
|
|
32135
|
+
try {
|
|
32136
|
+
if (!isAttached(config2.settings)) return local;
|
|
32137
|
+
const connection = config2.settings.controlPlane;
|
|
32138
|
+
if (connection === void 0) return local;
|
|
32139
|
+
const state = readControlPlaneCredentialState(config2.settingsDir, connection);
|
|
32140
|
+
if (!state.usable) return local;
|
|
32141
|
+
const client = createRemoteClient({
|
|
32142
|
+
endpoint: connection.endpoint,
|
|
32143
|
+
apiKey: state.credential.apiKey
|
|
32144
|
+
});
|
|
32145
|
+
const store = createPolicyStore(config2.dataDir);
|
|
32146
|
+
const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
|
|
32147
|
+
const forward = createForwardPolicy({ dir: config2.dataDir });
|
|
32148
|
+
return new AttachedDataGateway({
|
|
32149
|
+
local,
|
|
32150
|
+
client,
|
|
32151
|
+
dataDir: config2.dataDir,
|
|
32152
|
+
readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
|
|
32153
|
+
forward,
|
|
32154
|
+
posture: createPostureReporter({
|
|
32155
|
+
// THROUGH THE BREAKER, and wrapped HERE rather than around
|
|
32156
|
+
// `PostureReporter.send`. The reporter swallows every error by
|
|
32157
|
+
// contract, so a wrap outside it would hand `forward.run` a resolved
|
|
32158
|
+
// promise for a send that failed — recording a SUCCESS, clearing
|
|
32159
|
+
// `consecutiveFailures` and `lastFailure`, and telling `aka status` the
|
|
32160
|
+
// forward recovered when nothing did. Wrapping the raw client call puts
|
|
32161
|
+
// the breaker above the swallow, where it can see the truth.
|
|
32162
|
+
//
|
|
32163
|
+
// What it buys: once the breaker is open — the plane already confirmed
|
|
32164
|
+
// down by the gateway's own writes — this stops paying a request
|
|
32165
|
+
// timeout per throttle interval to re-learn it.
|
|
32166
|
+
report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
|
|
32167
|
+
store: postureStore,
|
|
32168
|
+
readStore: () => readStorePosture(config2.dbPath),
|
|
32169
|
+
hostname: () => hostname5(),
|
|
32170
|
+
now: () => Date.now()
|
|
32171
|
+
})
|
|
32172
|
+
});
|
|
32173
|
+
} catch {
|
|
32174
|
+
return local;
|
|
32175
|
+
}
|
|
32176
|
+
}
|
|
32177
|
+
|
|
30717
32178
|
// ../../packages/plugin-runtime/src/resolve.ts
|
|
30718
|
-
var
|
|
30719
|
-
var defaultGatewayFactory =
|
|
32179
|
+
var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
|
|
32180
|
+
var defaultGatewayFactory = configuredGatewayFactory;
|
|
30720
32181
|
function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
|
|
30721
32182
|
return gatewayFactory(config2, meta3);
|
|
30722
32183
|
}
|
|
30723
32184
|
|
|
30724
32185
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
30725
|
-
import { randomUUID as
|
|
32186
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
30726
32187
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
30727
32188
|
|
|
30728
32189
|
// src/remediation/redact.ts
|
|
30729
|
-
import { readFileSync as
|
|
30730
|
-
import { isAbsolute as isAbsolute2, relative, resolve } from "path";
|
|
32190
|
+
import { readFileSync as readFileSync15, realpathSync as realpathSync4, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
32191
|
+
import { isAbsolute as isAbsolute2, relative, resolve as resolve2 } from "path";
|
|
30731
32192
|
|
|
30732
32193
|
// src/history/transcripts.ts
|
|
30733
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
32194
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync14 } from "fs";
|
|
30734
32195
|
import { homedir as homedir3 } from "os";
|
|
30735
|
-
import { join as
|
|
32196
|
+
import { join as join22 } from "path";
|
|
30736
32197
|
function transcriptsDir(home) {
|
|
30737
|
-
return
|
|
32198
|
+
return join22(home ?? homedir3(), ".claude", "projects");
|
|
30738
32199
|
}
|
|
30739
32200
|
function isRecord(value) {
|
|
30740
32201
|
return typeof value === "object" && value !== null;
|
|
@@ -30943,7 +32404,7 @@ function platformRedactionScope(home) {
|
|
|
30943
32404
|
}
|
|
30944
32405
|
function realPathOrNull(path) {
|
|
30945
32406
|
try {
|
|
30946
|
-
return
|
|
32407
|
+
return realpathSync4(path);
|
|
30947
32408
|
} catch {
|
|
30948
32409
|
return null;
|
|
30949
32410
|
}
|
|
@@ -30955,7 +32416,7 @@ function isWithinRoot(realTarget, root) {
|
|
|
30955
32416
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
30956
32417
|
}
|
|
30957
32418
|
function resolveRedactableArtifact(filePath, scope) {
|
|
30958
|
-
const realTarget = realPathOrNull(
|
|
32419
|
+
const realTarget = realPathOrNull(resolve2(filePath));
|
|
30959
32420
|
if (realTarget === null) return null;
|
|
30960
32421
|
return scope.artifactRoots.some((root) => isWithinRoot(realTarget, root)) ? realTarget : null;
|
|
30961
32422
|
}
|
|
@@ -30967,13 +32428,13 @@ import {
|
|
|
30967
32428
|
fstatSync,
|
|
30968
32429
|
mkdirSync as mkdirSync4,
|
|
30969
32430
|
openSync as openSync2,
|
|
30970
|
-
readFileSync as
|
|
32431
|
+
readFileSync as readFileSync16,
|
|
30971
32432
|
readSync,
|
|
30972
32433
|
writeFileSync as writeFileSync8
|
|
30973
32434
|
} from "fs";
|
|
30974
|
-
import { join as
|
|
32435
|
+
import { join as join23 } from "path";
|
|
30975
32436
|
function offsetsDir(dataDir2) {
|
|
30976
|
-
return
|
|
32437
|
+
return join23(dataDir2, "usage-offsets");
|
|
30977
32438
|
}
|
|
30978
32439
|
var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
|
|
30979
32440
|
function safeSessionId(sessionId) {
|
|
@@ -30983,14 +32444,14 @@ function safeSessionId(sessionId) {
|
|
|
30983
32444
|
return createHash5("sha256").update(sessionId).digest("hex");
|
|
30984
32445
|
}
|
|
30985
32446
|
function offsetPath(dataDir2, sessionId) {
|
|
30986
|
-
return
|
|
32447
|
+
return join23(offsetsDir(dataDir2), safeSessionId(sessionId));
|
|
30987
32448
|
}
|
|
30988
32449
|
function readOffset(dataDir2, sessionId) {
|
|
30989
32450
|
try {
|
|
30990
|
-
const raw =
|
|
30991
|
-
const
|
|
30992
|
-
if (typeof
|
|
30993
|
-
const rec =
|
|
32451
|
+
const raw = readFileSync16(offsetPath(dataDir2, sessionId), "utf8");
|
|
32452
|
+
const parsed2 = JSON.parse(raw);
|
|
32453
|
+
if (typeof parsed2 === "object" && parsed2 !== null) {
|
|
32454
|
+
const rec = parsed2;
|
|
30994
32455
|
const offset = typeof rec.offset === "number" && Number.isFinite(rec.offset) && rec.offset >= 0 ? rec.offset : 0;
|
|
30995
32456
|
const lastPromptId = typeof rec.lastPromptId === "string" ? rec.lastPromptId : void 0;
|
|
30996
32457
|
return lastPromptId !== void 0 ? { offset, lastPromptId } : { offset };
|
|
@@ -31045,15 +32506,15 @@ function readTail(transcriptPath, startOffset) {
|
|
|
31045
32506
|
}
|
|
31046
32507
|
|
|
31047
32508
|
// src/history/tail-scrub.ts
|
|
31048
|
-
import { readFileSync as
|
|
32509
|
+
import { readFileSync as readFileSync17, renameSync as renameSync6, rmSync as rmSync7, statSync as statSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
31049
32510
|
var DEFAULT_MAX_SCRUB_BYTES = 32 * 1024 * 1024;
|
|
31050
32511
|
async function scrubTranscriptTail(filePath, deps) {
|
|
31051
32512
|
try {
|
|
31052
32513
|
const realPath = resolveRedactableArtifact(filePath, deps.scope);
|
|
31053
32514
|
if (realPath === null) return null;
|
|
31054
|
-
const statBefore =
|
|
32515
|
+
const statBefore = statSync10(realPath);
|
|
31055
32516
|
if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
|
|
31056
|
-
const content =
|
|
32517
|
+
const content = readFileSync17(realPath, "utf8");
|
|
31057
32518
|
const lines = content.split("\n");
|
|
31058
32519
|
let rewritten = 0;
|
|
31059
32520
|
for (const [i, line] of lines.entries()) {
|
|
@@ -31068,15 +32529,15 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
31068
32529
|
const tmpPath = `${realPath}.aka-scrub.tmp`;
|
|
31069
32530
|
try {
|
|
31070
32531
|
writeFileSync9(tmpPath, lines.join("\n"), { mode: statBefore.mode & 511 });
|
|
31071
|
-
const statNow =
|
|
32532
|
+
const statNow = statSync10(realPath);
|
|
31072
32533
|
if (statNow.size !== statBefore.size || statNow.mtimeMs !== statBefore.mtimeMs) {
|
|
31073
|
-
|
|
32534
|
+
rmSync7(tmpPath, { force: true, recursive: true });
|
|
31074
32535
|
return null;
|
|
31075
32536
|
}
|
|
31076
32537
|
renameSync6(tmpPath, realPath);
|
|
31077
32538
|
} catch {
|
|
31078
32539
|
try {
|
|
31079
|
-
|
|
32540
|
+
rmSync7(tmpPath, { force: true, recursive: true });
|
|
31080
32541
|
} catch {
|
|
31081
32542
|
}
|
|
31082
32543
|
return null;
|