@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/post-tool-use.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
|
|
@@ -491,14 +491,31 @@ var require_ignore = __commonJS({
|
|
|
491
491
|
}
|
|
492
492
|
});
|
|
493
493
|
|
|
494
|
-
// ../../packages/plugin-
|
|
495
|
-
|
|
496
|
-
|
|
494
|
+
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
495
|
+
function statusOf(err) {
|
|
496
|
+
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
497
|
+
const { status } = err;
|
|
498
|
+
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
499
|
+
return status >= 100 && status <= 599 ? status : null;
|
|
500
|
+
}
|
|
501
|
+
function classifyFailure(err) {
|
|
502
|
+
switch (statusOf(err)) {
|
|
503
|
+
case 401:
|
|
504
|
+
return "unauthorized";
|
|
505
|
+
case 403:
|
|
506
|
+
return "forbidden";
|
|
507
|
+
default:
|
|
508
|
+
return "unreachable";
|
|
509
|
+
}
|
|
510
|
+
}
|
|
497
511
|
|
|
498
|
-
// ../../packages/
|
|
499
|
-
import {
|
|
500
|
-
import { join as
|
|
501
|
-
|
|
512
|
+
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
513
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
514
|
+
import { join as join10 } from "path";
|
|
515
|
+
|
|
516
|
+
// ../../packages/persistence/src/control-plane-credential.ts
|
|
517
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
|
|
518
|
+
import { join } from "path";
|
|
502
519
|
|
|
503
520
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
504
521
|
var SQLITE_MIGRATIONS = [
|
|
@@ -16202,6 +16219,125 @@ var ConfigScanRecord = external_exports.object({
|
|
|
16202
16219
|
findings: external_exports.array(ConfigPostureFindingInput).optional()
|
|
16203
16220
|
});
|
|
16204
16221
|
|
|
16222
|
+
// ../../packages/schema/src/zod/control-plane.ts
|
|
16223
|
+
var ATTACHED_CREDENTIAL_FILENAME = "control-plane-credential.json";
|
|
16224
|
+
var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
|
|
16225
|
+
var AttachedCredential = external_exports.object({
|
|
16226
|
+
specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
|
|
16227
|
+
// The control-plane endpoint this credential was minted against.
|
|
16228
|
+
endpoint: external_exports.string().min(1),
|
|
16229
|
+
// The bearer credential itself. Never logged, never rendered — status
|
|
16230
|
+
// surfaces show `keyPrefix` and nothing else.
|
|
16231
|
+
apiKey: external_exports.string().min(1),
|
|
16232
|
+
// First few characters of the key, safe to display so a user can match the
|
|
16233
|
+
// credential against their organization's key list.
|
|
16234
|
+
keyPrefix: external_exports.string().min(1).max(16).optional(),
|
|
16235
|
+
mintedAt: external_exports.iso.datetime().optional()
|
|
16236
|
+
});
|
|
16237
|
+
var MAX_DATE_MS = 253402300799999;
|
|
16238
|
+
var MAX_INT4 = 2147483647;
|
|
16239
|
+
var StorePosturePack = external_exports.object({
|
|
16240
|
+
packId: external_exports.string().min(1),
|
|
16241
|
+
// 'namespace/packId'
|
|
16242
|
+
version: external_exports.string().min(1),
|
|
16243
|
+
enabled: external_exports.boolean(),
|
|
16244
|
+
// Stringified pass-through of the local store's `installed_packs.updated_at`
|
|
16245
|
+
// — the column format is store-version-dependent (epoch millis vs ISO), so
|
|
16246
|
+
// the wire shape assumes neither.
|
|
16247
|
+
updatedAt: external_exports.string().nullable()
|
|
16248
|
+
}).meta({ id: "StorePosturePack" });
|
|
16249
|
+
var StorePosturePolicyCounts = external_exports.object({
|
|
16250
|
+
total: external_exports.number().int().min(0),
|
|
16251
|
+
disabled: external_exports.number().int().min(0),
|
|
16252
|
+
// Exhaustive per-action map; the builder pre-fills every action with 0.
|
|
16253
|
+
//
|
|
16254
|
+
// Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
|
|
16255
|
+
// enforces exhaustiveness either way, but z.record emits `propertyNames` +
|
|
16256
|
+
// `additionalProperties` into a generated schema document, and a type
|
|
16257
|
+
// generator renders THAT with every key optional — a sender built against
|
|
16258
|
+
// the generated type would typecheck and still be rejected at runtime. An
|
|
16259
|
+
// explicit object emits `properties` + `required`, so generated types
|
|
16260
|
+
// demand all five.
|
|
16261
|
+
//
|
|
16262
|
+
// `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
|
|
16263
|
+
// ActionTaken member is a COMPILE error here instead of silent drift.
|
|
16264
|
+
// `.strict()` is load-bearing — it rejects an unknown action key, which a
|
|
16265
|
+
// bare object would silently STRIP, accepting a miscounted map as valid.
|
|
16266
|
+
byAction: external_exports.object({
|
|
16267
|
+
warn: external_exports.number().int().min(0),
|
|
16268
|
+
redact: external_exports.number().int().min(0),
|
|
16269
|
+
block: external_exports.number().int().min(0),
|
|
16270
|
+
allow: external_exports.number().int().min(0),
|
|
16271
|
+
log: external_exports.number().int().min(0)
|
|
16272
|
+
}).strict()
|
|
16273
|
+
}).meta({ id: "StorePosturePolicyCounts" });
|
|
16274
|
+
var StorePosturePlugin = external_exports.object({
|
|
16275
|
+
/** Package name of the reporting plugin. */
|
|
16276
|
+
package: external_exports.string().min(1).max(200),
|
|
16277
|
+
version: external_exports.string().min(1).max(64),
|
|
16278
|
+
/** Version of the bundled core, when the build records one separately. */
|
|
16279
|
+
ossVersion: external_exports.string().max(64).nullable(),
|
|
16280
|
+
/**
|
|
16281
|
+
* `version` of the policy bundle this machine last fetched. Bounded at 200
|
|
16282
|
+
* rather than the 64 a bare sha256 hex digest needs today, so a later
|
|
16283
|
+
* format with an algorithm prefix does not start rejecting the channel.
|
|
16284
|
+
*/
|
|
16285
|
+
policyBundleVersion: external_exports.string().max(200).nullable(),
|
|
16286
|
+
/** Epoch millis, on the CLIENT clock, of that fetch. */
|
|
16287
|
+
policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
|
|
16288
|
+
}).meta({ id: "StorePosturePlugin" });
|
|
16289
|
+
var StorePostureSnapshot = external_exports.object({
|
|
16290
|
+
deviceId: external_exports.guid(),
|
|
16291
|
+
hostname: external_exports.string().min(1).max(253),
|
|
16292
|
+
// Epoch millis on the CLIENT clock. Bounded by what a receiving store
|
|
16293
|
+
// accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
|
|
16294
|
+
capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
|
|
16295
|
+
// False is a measurement, not an error state: "no local store exists on
|
|
16296
|
+
// this machine".
|
|
16297
|
+
storePresent: external_exports.boolean(),
|
|
16298
|
+
schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
|
|
16299
|
+
// PRAGMA user_version
|
|
16300
|
+
findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
|
|
16301
|
+
// Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
|
|
16302
|
+
// bound does and does not do. Worth stating for these two specifically:
|
|
16303
|
+
// they are read from the local store's own ROWS rather than from this
|
|
16304
|
+
// machine's clock, so a damaged or hand-edited store is enough to produce
|
|
16305
|
+
// an out-of-range value with no clock skew involved.
|
|
16306
|
+
findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
|
|
16307
|
+
findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
|
|
16308
|
+
packs: external_exports.array(StorePosturePack).max(500),
|
|
16309
|
+
policyCounts: StorePosturePolicyCounts,
|
|
16310
|
+
// OPTIONAL, not nullable: a reporter that predates this member keeps
|
|
16311
|
+
// getting its 200 without a payload change.
|
|
16312
|
+
plugin: StorePosturePlugin.optional()
|
|
16313
|
+
}).meta({ id: "StorePostureSnapshot" });
|
|
16314
|
+
var CAPTURE_VERSION_PREFIX = "capture/";
|
|
16315
|
+
var RecordAuditEventRequest = AuditEventInput.extend({
|
|
16316
|
+
inspections: external_exports.array(ToolCallInspection).default([])
|
|
16317
|
+
}).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
|
|
16318
|
+
message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
|
|
16319
|
+
path: ["inspections"]
|
|
16320
|
+
}).meta({ id: "RecordAuditEventRequest" });
|
|
16321
|
+
var IngestAck = external_exports.object({
|
|
16322
|
+
accepted: external_exports.number().int().nonnegative(),
|
|
16323
|
+
duplicates: external_exports.number().int().nonnegative()
|
|
16324
|
+
});
|
|
16325
|
+
var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
|
|
16326
|
+
var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
|
|
16327
|
+
var PluginWhoami = external_exports.object({
|
|
16328
|
+
tenantName: printable(200),
|
|
16329
|
+
userEmail: printable(320),
|
|
16330
|
+
role: printable(64),
|
|
16331
|
+
keyKind: printable(64),
|
|
16332
|
+
serverTime: printable(64)
|
|
16333
|
+
});
|
|
16334
|
+
var ControlPlaneErrorBody = external_exports.object({
|
|
16335
|
+
error: external_exports.object({
|
|
16336
|
+
code: external_exports.string().optional(),
|
|
16337
|
+
message: external_exports.string().optional()
|
|
16338
|
+
}).optional()
|
|
16339
|
+
});
|
|
16340
|
+
|
|
16205
16341
|
// ../../packages/schema/src/zod/registry.ts
|
|
16206
16342
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
16207
16343
|
var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -16504,15 +16640,15 @@ function summaryToDetectionListItem(s) {
|
|
|
16504
16640
|
}
|
|
16505
16641
|
function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
16506
16642
|
const rules = row.rules.flatMap((r) => {
|
|
16507
|
-
const
|
|
16508
|
-
if (!
|
|
16643
|
+
const parsed2 = Matcher.safeParse(r.matcher);
|
|
16644
|
+
if (!parsed2.success) return [];
|
|
16509
16645
|
return [
|
|
16510
16646
|
{
|
|
16511
16647
|
id: r.id,
|
|
16512
16648
|
name: r.name,
|
|
16513
16649
|
category: r.category,
|
|
16514
16650
|
severity: r.severity,
|
|
16515
|
-
matcher:
|
|
16651
|
+
matcher: parsed2.data
|
|
16516
16652
|
}
|
|
16517
16653
|
];
|
|
16518
16654
|
});
|
|
@@ -17158,8 +17294,8 @@ function toApiAction(dbVal) {
|
|
|
17158
17294
|
}
|
|
17159
17295
|
function toApiCategory(dbVal) {
|
|
17160
17296
|
if (dbVal === "code_context") return "source_code";
|
|
17161
|
-
const
|
|
17162
|
-
return
|
|
17297
|
+
const parsed2 = FindingCategory.safeParse(dbVal);
|
|
17298
|
+
return parsed2.success ? parsed2.data : "custom";
|
|
17163
17299
|
}
|
|
17164
17300
|
function toApiProvider(sourceTool) {
|
|
17165
17301
|
return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
|
|
@@ -17797,6 +17933,9 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17797
17933
|
function defaultWorkspaceSettings() {
|
|
17798
17934
|
return WorkspaceSettings.parse({});
|
|
17799
17935
|
}
|
|
17936
|
+
function isAttached(settings) {
|
|
17937
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
17938
|
+
}
|
|
17800
17939
|
function toInventoryRow(input, id, now) {
|
|
17801
17940
|
return {
|
|
17802
17941
|
id,
|
|
@@ -18064,8 +18203,8 @@ function builtinPolicyIsReversible(id) {
|
|
|
18064
18203
|
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
18065
18204
|
}
|
|
18066
18205
|
function policyIdIsReversible(policyId) {
|
|
18067
|
-
const
|
|
18068
|
-
const id =
|
|
18206
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18207
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
18069
18208
|
return builtinPolicyIsReversible(id);
|
|
18070
18209
|
}
|
|
18071
18210
|
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
@@ -18076,8 +18215,8 @@ var BUILTIN_POLICIES = Object.fromEntries(
|
|
|
18076
18215
|
);
|
|
18077
18216
|
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
18078
18217
|
function policyIdToAction(policyId) {
|
|
18079
|
-
const
|
|
18080
|
-
const id =
|
|
18218
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18219
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
18081
18220
|
return BUILTIN_POLICIES[id].action;
|
|
18082
18221
|
}
|
|
18083
18222
|
var UsedByItem = external_exports.object({
|
|
@@ -18520,53 +18659,6 @@ function reviewSeverityRank(reasons) {
|
|
|
18520
18659
|
return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
|
|
18521
18660
|
}
|
|
18522
18661
|
|
|
18523
|
-
// ../../packages/persistence/src/ids.ts
|
|
18524
|
-
import { createHash } from "crypto";
|
|
18525
|
-
function sha256Hex(input) {
|
|
18526
|
-
return createHash("sha256").update(input).digest("hex");
|
|
18527
|
-
}
|
|
18528
|
-
function inventoryId(objectType, identityKey) {
|
|
18529
|
-
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
18530
|
-
}
|
|
18531
|
-
function sourceProjectId(url2) {
|
|
18532
|
-
return sha256Hex(canonicalIdentity(["source_project", url2]));
|
|
18533
|
-
}
|
|
18534
|
-
function classifiedDataId(cls) {
|
|
18535
|
-
return sha256Hex(canonicalIdentity(["classified_data", cls]));
|
|
18536
|
-
}
|
|
18537
|
-
function inspectionDefinitionId(ruleId, version2) {
|
|
18538
|
-
return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
|
|
18539
|
-
}
|
|
18540
|
-
function llmCallId(sessionId, messageId) {
|
|
18541
|
-
return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
|
|
18542
|
-
}
|
|
18543
|
-
function toolCallId(sessionId, toolUseId) {
|
|
18544
|
-
return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
|
|
18545
|
-
}
|
|
18546
|
-
function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
|
|
18547
|
-
return sha256Hex(
|
|
18548
|
-
canonicalIdentity([
|
|
18549
|
-
"inspection_finding",
|
|
18550
|
-
auditEventId,
|
|
18551
|
-
ruleId,
|
|
18552
|
-
String(spanStart),
|
|
18553
|
-
String(spanEnd)
|
|
18554
|
-
])
|
|
18555
|
-
);
|
|
18556
|
-
}
|
|
18557
|
-
var NO_SESSION = "no_session";
|
|
18558
|
-
var NO_PATH = "no_path";
|
|
18559
|
-
function captureId(sessionId, contentHash, filePath = null) {
|
|
18560
|
-
return sha256Hex(
|
|
18561
|
-
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
18562
|
-
);
|
|
18563
|
-
}
|
|
18564
|
-
|
|
18565
|
-
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18566
|
-
import { randomUUID } from "crypto";
|
|
18567
|
-
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18568
|
-
import { basename, dirname, join } from "path";
|
|
18569
|
-
|
|
18570
18662
|
// ../../packages/persistence/src/paths.ts
|
|
18571
18663
|
import {
|
|
18572
18664
|
chmodSync,
|
|
@@ -18692,7 +18784,123 @@ function publishByLink(tmp, file2, data) {
|
|
|
18692
18784
|
}
|
|
18693
18785
|
}
|
|
18694
18786
|
|
|
18787
|
+
// ../../packages/persistence/src/control-plane-credential.ts
|
|
18788
|
+
function controlPlaneCredentialPath(settingsDir2) {
|
|
18789
|
+
return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
|
|
18790
|
+
}
|
|
18791
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
18792
|
+
function isSafeEndpoint(endpoint) {
|
|
18793
|
+
let parsed2;
|
|
18794
|
+
try {
|
|
18795
|
+
parsed2 = new URL(endpoint);
|
|
18796
|
+
} catch {
|
|
18797
|
+
return false;
|
|
18798
|
+
}
|
|
18799
|
+
if (parsed2.protocol === "https:") return true;
|
|
18800
|
+
return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
|
|
18801
|
+
}
|
|
18802
|
+
function repairOrRefuseMode(file2) {
|
|
18803
|
+
const link = lstatSync2(file2, { throwIfNoEntry: false });
|
|
18804
|
+
if (link === void 0) return "absent";
|
|
18805
|
+
if (link.isSymbolicLink()) return "untrusted";
|
|
18806
|
+
const stat = statSync(file2, { throwIfNoEntry: false });
|
|
18807
|
+
if (stat === void 0) return "absent";
|
|
18808
|
+
const uid = process.getuid?.();
|
|
18809
|
+
if (uid !== void 0 && stat.uid !== uid) return "untrusted";
|
|
18810
|
+
if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
|
|
18811
|
+
try {
|
|
18812
|
+
chmodSync2(file2, DATA_FILE_MODE);
|
|
18813
|
+
} catch {
|
|
18814
|
+
return "untrusted";
|
|
18815
|
+
}
|
|
18816
|
+
}
|
|
18817
|
+
return "ok";
|
|
18818
|
+
}
|
|
18819
|
+
function readControlPlaneCredentialState(settingsDir2, connection) {
|
|
18820
|
+
const file2 = controlPlaneCredentialPath(settingsDir2);
|
|
18821
|
+
let raw;
|
|
18822
|
+
const gate = repairOrRefuseMode(file2);
|
|
18823
|
+
if (gate === "absent") return { usable: false, reason: "absent" };
|
|
18824
|
+
if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
|
|
18825
|
+
try {
|
|
18826
|
+
raw = readFileSync(file2, "utf8");
|
|
18827
|
+
} catch (err) {
|
|
18828
|
+
const code = err.code;
|
|
18829
|
+
return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
|
|
18830
|
+
}
|
|
18831
|
+
let parsed2;
|
|
18832
|
+
try {
|
|
18833
|
+
parsed2 = JSON.parse(raw);
|
|
18834
|
+
} catch {
|
|
18835
|
+
return { usable: false, reason: "malformed" };
|
|
18836
|
+
}
|
|
18837
|
+
const result = AttachedCredential.safeParse(parsed2);
|
|
18838
|
+
if (!result.success) return { usable: false, reason: "malformed" };
|
|
18839
|
+
if (!isSafeEndpoint(result.data.endpoint)) {
|
|
18840
|
+
return { usable: false, reason: "unsafe-endpoint" };
|
|
18841
|
+
}
|
|
18842
|
+
if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
|
|
18843
|
+
return {
|
|
18844
|
+
usable: false,
|
|
18845
|
+
reason: "endpoint-mismatch",
|
|
18846
|
+
credentialEndpoint: result.data.endpoint,
|
|
18847
|
+
settingsEndpoint: connection.endpoint
|
|
18848
|
+
};
|
|
18849
|
+
}
|
|
18850
|
+
return { usable: true, credential: result.data };
|
|
18851
|
+
}
|
|
18852
|
+
|
|
18853
|
+
// ../../packages/persistence/src/database.ts
|
|
18854
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
18855
|
+
import { join as join3, sep } from "path";
|
|
18856
|
+
import { DatabaseSync } from "node:sqlite";
|
|
18857
|
+
|
|
18858
|
+
// ../../packages/persistence/src/ids.ts
|
|
18859
|
+
import { createHash } from "crypto";
|
|
18860
|
+
function sha256Hex(input) {
|
|
18861
|
+
return createHash("sha256").update(input).digest("hex");
|
|
18862
|
+
}
|
|
18863
|
+
function inventoryId(objectType, identityKey) {
|
|
18864
|
+
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
18865
|
+
}
|
|
18866
|
+
function sourceProjectId(url2) {
|
|
18867
|
+
return sha256Hex(canonicalIdentity(["source_project", url2]));
|
|
18868
|
+
}
|
|
18869
|
+
function classifiedDataId(cls) {
|
|
18870
|
+
return sha256Hex(canonicalIdentity(["classified_data", cls]));
|
|
18871
|
+
}
|
|
18872
|
+
function inspectionDefinitionId(ruleId, version2) {
|
|
18873
|
+
return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
|
|
18874
|
+
}
|
|
18875
|
+
function llmCallId(sessionId, messageId) {
|
|
18876
|
+
return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
|
|
18877
|
+
}
|
|
18878
|
+
function toolCallId(sessionId, toolUseId) {
|
|
18879
|
+
return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
|
|
18880
|
+
}
|
|
18881
|
+
function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
|
|
18882
|
+
return sha256Hex(
|
|
18883
|
+
canonicalIdentity([
|
|
18884
|
+
"inspection_finding",
|
|
18885
|
+
auditEventId,
|
|
18886
|
+
ruleId,
|
|
18887
|
+
String(spanStart),
|
|
18888
|
+
String(spanEnd)
|
|
18889
|
+
])
|
|
18890
|
+
);
|
|
18891
|
+
}
|
|
18892
|
+
var NO_SESSION = "no_session";
|
|
18893
|
+
var NO_PATH = "no_path";
|
|
18894
|
+
function captureId(sessionId, contentHash, filePath = null) {
|
|
18895
|
+
return sha256Hex(
|
|
18896
|
+
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
18897
|
+
);
|
|
18898
|
+
}
|
|
18899
|
+
|
|
18695
18900
|
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18901
|
+
import { randomUUID } from "crypto";
|
|
18902
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
|
|
18903
|
+
import { basename, dirname, join as join2 } from "path";
|
|
18696
18904
|
function backupPath(file2, tag) {
|
|
18697
18905
|
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18698
18906
|
}
|
|
@@ -18702,15 +18910,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
|
|
|
18702
18910
|
var SNAPSHOT_STAGING_COPY = "copy";
|
|
18703
18911
|
function createSnapshotStaging(backup) {
|
|
18704
18912
|
const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
|
|
18705
|
-
|
|
18913
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18706
18914
|
mkdirOwnerOnlySync(stage);
|
|
18707
18915
|
tightenDir(stage);
|
|
18708
|
-
return { stage, copy:
|
|
18916
|
+
return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
|
|
18709
18917
|
}
|
|
18710
18918
|
function idleMs(entry) {
|
|
18711
|
-
for (const candidate of [
|
|
18919
|
+
for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
|
|
18712
18920
|
try {
|
|
18713
|
-
return Date.now() -
|
|
18921
|
+
return Date.now() - statSync2(candidate).mtimeMs;
|
|
18714
18922
|
} catch {
|
|
18715
18923
|
}
|
|
18716
18924
|
}
|
|
@@ -18727,11 +18935,11 @@ function reapStalePartials(file2) {
|
|
|
18727
18935
|
}
|
|
18728
18936
|
for (const name of entries) {
|
|
18729
18937
|
if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
|
|
18730
|
-
const staging =
|
|
18938
|
+
const staging = join2(dir, name);
|
|
18731
18939
|
try {
|
|
18732
18940
|
const idle = idleMs(staging);
|
|
18733
18941
|
if (idle !== null && idle > STALE_PARTIAL_MS) {
|
|
18734
|
-
|
|
18942
|
+
rmSync3(staging, { recursive: true, force: true });
|
|
18735
18943
|
}
|
|
18736
18944
|
} catch {
|
|
18737
18945
|
}
|
|
@@ -18745,13 +18953,13 @@ function snapshotStore(db, backup) {
|
|
|
18745
18953
|
renameSync2(copy, backup);
|
|
18746
18954
|
} catch (error51) {
|
|
18747
18955
|
try {
|
|
18748
|
-
|
|
18956
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18749
18957
|
} catch {
|
|
18750
18958
|
}
|
|
18751
18959
|
throw error51;
|
|
18752
18960
|
}
|
|
18753
18961
|
try {
|
|
18754
|
-
|
|
18962
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18755
18963
|
} catch {
|
|
18756
18964
|
}
|
|
18757
18965
|
}
|
|
@@ -18766,7 +18974,7 @@ function moveStoreAside(file2, backup) {
|
|
|
18766
18974
|
renameSync2(sidecar, moved);
|
|
18767
18975
|
undo.push([moved, sidecar]);
|
|
18768
18976
|
} catch {
|
|
18769
|
-
|
|
18977
|
+
rmSync3(sidecar, { force: true });
|
|
18770
18978
|
}
|
|
18771
18979
|
}
|
|
18772
18980
|
} catch (error51) {
|
|
@@ -18782,14 +18990,14 @@ function moveStoreAside(file2, backup) {
|
|
|
18782
18990
|
}
|
|
18783
18991
|
function discardStore(file2, backup) {
|
|
18784
18992
|
try {
|
|
18785
|
-
|
|
18993
|
+
rmSync3(file2, { force: true });
|
|
18786
18994
|
for (const sidecar of dbSidecars(file2)) {
|
|
18787
|
-
|
|
18995
|
+
rmSync3(sidecar, { force: true });
|
|
18788
18996
|
}
|
|
18789
18997
|
} catch (error51) {
|
|
18790
18998
|
if (existsSync(file2)) {
|
|
18791
18999
|
try {
|
|
18792
|
-
|
|
19000
|
+
rmSync3(backup, { force: true });
|
|
18793
19001
|
} catch {
|
|
18794
19002
|
}
|
|
18795
19003
|
}
|
|
@@ -19021,10 +19229,31 @@ function applyMigrations(db, file2) {
|
|
|
19021
19229
|
if (drained) applyLegacyDropMigration(db, file2);
|
|
19022
19230
|
}
|
|
19023
19231
|
}
|
|
19232
|
+
function readLegacyTables(db) {
|
|
19233
|
+
let holdsRows = false;
|
|
19234
|
+
const marks = [];
|
|
19235
|
+
for (const table of ["events", "findings"]) {
|
|
19236
|
+
try {
|
|
19237
|
+
const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table}`).get();
|
|
19238
|
+
if (row === void 0) {
|
|
19239
|
+
holdsRows = true;
|
|
19240
|
+
marks.push(`${table}:unreadable`);
|
|
19241
|
+
continue;
|
|
19242
|
+
}
|
|
19243
|
+
if (row.n > 0) holdsRows = true;
|
|
19244
|
+
marks.push(`${table}:${String(row.n)}:${String(row.hi)}`);
|
|
19245
|
+
} catch {
|
|
19246
|
+
holdsRows = true;
|
|
19247
|
+
marks.push(`${table}:unreadable`);
|
|
19248
|
+
}
|
|
19249
|
+
}
|
|
19250
|
+
return { holdsRows, mark: marks.join("|") };
|
|
19251
|
+
}
|
|
19024
19252
|
function applyLegacyDropMigration(db, file2) {
|
|
19025
19253
|
const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
|
|
19026
19254
|
if (!migration) return;
|
|
19027
|
-
|
|
19255
|
+
const before = file2 === void 0 ? void 0 : readLegacyTables(db);
|
|
19256
|
+
if (file2 !== void 0 && before?.holdsRows === true) {
|
|
19028
19257
|
try {
|
|
19029
19258
|
backupBeforeLegacyDrop(db, file2);
|
|
19030
19259
|
} catch (error51) {
|
|
@@ -19038,6 +19267,12 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
19038
19267
|
() => {
|
|
19039
19268
|
const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
|
|
19040
19269
|
if (alreadyDropped) return;
|
|
19270
|
+
if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
|
|
19271
|
+
akaWarn(
|
|
19272
|
+
"legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
|
|
19273
|
+
);
|
|
19274
|
+
return;
|
|
19275
|
+
}
|
|
19041
19276
|
for (const statement of splitStatements(migration.sql)) {
|
|
19042
19277
|
db.exec(statement);
|
|
19043
19278
|
}
|
|
@@ -19392,8 +19627,8 @@ function safeJson(s, fallback) {
|
|
|
19392
19627
|
function parseJsonObject(s) {
|
|
19393
19628
|
if (s == null) return void 0;
|
|
19394
19629
|
try {
|
|
19395
|
-
const
|
|
19396
|
-
if (typeof
|
|
19630
|
+
const parsed2 = JSON.parse(s);
|
|
19631
|
+
if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
|
|
19397
19632
|
} catch {
|
|
19398
19633
|
}
|
|
19399
19634
|
return void 0;
|
|
@@ -19404,16 +19639,16 @@ function encodeKeysetCursor(payload) {
|
|
|
19404
19639
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19405
19640
|
}
|
|
19406
19641
|
function decodeKeysetCursor(cursor) {
|
|
19407
|
-
const
|
|
19408
|
-
if (
|
|
19642
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19643
|
+
if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19409
19644
|
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19410
19645
|
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19411
19646
|
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19412
19647
|
// outcome a cursor that does not decode must never produce, since the
|
|
19413
19648
|
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19414
19649
|
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19415
|
-
Number.isInteger(
|
|
19416
|
-
return
|
|
19650
|
+
Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
|
|
19651
|
+
return parsed2;
|
|
19417
19652
|
}
|
|
19418
19653
|
return null;
|
|
19419
19654
|
}
|
|
@@ -19478,18 +19713,18 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
19478
19713
|
};
|
|
19479
19714
|
function safeParseStringArray(raw) {
|
|
19480
19715
|
if (!raw) return [];
|
|
19481
|
-
const
|
|
19482
|
-
return Array.isArray(
|
|
19716
|
+
const parsed2 = safeJson(raw, null);
|
|
19717
|
+
return Array.isArray(parsed2) ? parsed2 : [];
|
|
19483
19718
|
}
|
|
19484
19719
|
var DEFAULT_HARNESS = HARNESS.ClaudeCode;
|
|
19485
19720
|
function toHarness(raw) {
|
|
19486
|
-
const
|
|
19487
|
-
return
|
|
19721
|
+
const parsed2 = Harness.safeParse(raw);
|
|
19722
|
+
return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
|
|
19488
19723
|
}
|
|
19489
19724
|
function resolveLifecycle(row, lastActivityMs, nowMs) {
|
|
19490
19725
|
if (row.status) {
|
|
19491
|
-
const
|
|
19492
|
-
if (
|
|
19726
|
+
const parsed2 = SessionStatus.safeParse(row.status);
|
|
19727
|
+
if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
|
|
19493
19728
|
}
|
|
19494
19729
|
if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
|
|
19495
19730
|
if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
|
|
@@ -20448,9 +20683,9 @@ var SqliteDetectionsRepository = class {
|
|
|
20448
20683
|
const ruleIds = /* @__PURE__ */ new Set();
|
|
20449
20684
|
for (const r of rows) {
|
|
20450
20685
|
if (intToBool(r.enabled)) active += 1;
|
|
20451
|
-
const
|
|
20452
|
-
rules +=
|
|
20453
|
-
for (const rule of
|
|
20686
|
+
const parsed2 = parseRules(r.rulesJson);
|
|
20687
|
+
rules += parsed2.length;
|
|
20688
|
+
for (const rule of parsed2) {
|
|
20454
20689
|
if (typeof rule.id === "string") ruleIds.add(rule.id);
|
|
20455
20690
|
}
|
|
20456
20691
|
}
|
|
@@ -20984,12 +21219,12 @@ function encodeGroupCursor(group) {
|
|
|
20984
21219
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20985
21220
|
}
|
|
20986
21221
|
function decodeGroupCursor(cursor) {
|
|
20987
|
-
const
|
|
20988
|
-
if (
|
|
21222
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
21223
|
+
if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
|
|
20989
21224
|
return {
|
|
20990
|
-
severity:
|
|
20991
|
-
latestDetectedAt:
|
|
20992
|
-
id:
|
|
21225
|
+
severity: parsed2.sev,
|
|
21226
|
+
latestDetectedAt: parsed2.t,
|
|
21227
|
+
id: parsed2.id
|
|
20993
21228
|
};
|
|
20994
21229
|
}
|
|
20995
21230
|
return null;
|
|
@@ -22123,16 +22358,16 @@ var SqliteInstalledPacksRepository = class {
|
|
|
22123
22358
|
continue;
|
|
22124
22359
|
}
|
|
22125
22360
|
for (const entry of raw) {
|
|
22126
|
-
const
|
|
22127
|
-
if (
|
|
22128
|
-
out.rules.push(
|
|
22129
|
-
out.ruleActions.set(
|
|
22130
|
-
out.ruleVersions.set(
|
|
22131
|
-
if (reversible) out.reversibleRules.add(
|
|
22132
|
-
else out.reversibleRules.delete(
|
|
22361
|
+
const parsed2 = Rule.safeParse(entry);
|
|
22362
|
+
if (parsed2.success) {
|
|
22363
|
+
out.rules.push(parsed2.data);
|
|
22364
|
+
out.ruleActions.set(parsed2.data.id, action);
|
|
22365
|
+
out.ruleVersions.set(parsed2.data.id, row.version);
|
|
22366
|
+
if (reversible) out.reversibleRules.add(parsed2.data.id);
|
|
22367
|
+
else out.reversibleRules.delete(parsed2.data.id);
|
|
22133
22368
|
} else {
|
|
22134
22369
|
out.invalidRules += 1;
|
|
22135
|
-
reject(pack, printableRuleId(entry), firstIssueReason(
|
|
22370
|
+
reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
|
|
22136
22371
|
}
|
|
22137
22372
|
}
|
|
22138
22373
|
}
|
|
@@ -23522,15 +23757,15 @@ function encodeReuseCursor(payload) {
|
|
|
23522
23757
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23523
23758
|
}
|
|
23524
23759
|
function decodeReuseCursor(cursor) {
|
|
23525
|
-
const
|
|
23526
|
-
if (
|
|
23760
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23761
|
+
if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23527
23762
|
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23528
23763
|
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23529
23764
|
// malformed cursor must never produce, since restarting from the top is the
|
|
23530
23765
|
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23531
23766
|
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23532
|
-
Number.isInteger(
|
|
23533
|
-
return { occurrences:
|
|
23767
|
+
Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
|
|
23768
|
+
return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
|
|
23534
23769
|
}
|
|
23535
23770
|
return null;
|
|
23536
23771
|
}
|
|
@@ -25259,7 +25494,7 @@ function openAndInitialize(file2) {
|
|
|
25259
25494
|
}
|
|
25260
25495
|
function openLocalDatabase(dir) {
|
|
25261
25496
|
ensureDataDirSync(dir);
|
|
25262
|
-
const file2 =
|
|
25497
|
+
const file2 = join3(dir, DB_FILENAME);
|
|
25263
25498
|
reapStalePartials(file2);
|
|
25264
25499
|
const {
|
|
25265
25500
|
db,
|
|
@@ -25515,9 +25750,9 @@ import {
|
|
|
25515
25750
|
closeSync,
|
|
25516
25751
|
existsSync as existsSync2,
|
|
25517
25752
|
openSync,
|
|
25518
|
-
readFileSync,
|
|
25519
|
-
rmSync as
|
|
25520
|
-
statSync as
|
|
25753
|
+
readFileSync as readFileSync2,
|
|
25754
|
+
rmSync as rmSync4,
|
|
25755
|
+
statSync as statSync3,
|
|
25521
25756
|
writeFileSync as writeFileSync2
|
|
25522
25757
|
} from "fs";
|
|
25523
25758
|
import { hostname as hostname3 } from "os";
|
|
@@ -25535,20 +25770,20 @@ function computeFindingKey(input) {
|
|
|
25535
25770
|
|
|
25536
25771
|
// ../../packages/persistence/src/fingerprint.ts
|
|
25537
25772
|
import { createHmac, randomBytes } from "crypto";
|
|
25538
|
-
import { existsSync as existsSync3, readFileSync as
|
|
25539
|
-
import { join as
|
|
25773
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
25774
|
+
import { join as join4 } from "path";
|
|
25540
25775
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
25541
25776
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
25542
25777
|
var KEY_MATERIAL_BYTES = 32;
|
|
25543
25778
|
function keyFilePath(dataDir2) {
|
|
25544
|
-
return
|
|
25779
|
+
return join4(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
25545
25780
|
}
|
|
25546
25781
|
function parseKeyFile(raw) {
|
|
25547
|
-
const
|
|
25548
|
-
if (typeof
|
|
25782
|
+
const parsed2 = JSON.parse(raw);
|
|
25783
|
+
if (typeof parsed2 !== "object" || parsed2 === null) {
|
|
25549
25784
|
throw new Error("exception key file is corrupt: not a JSON object");
|
|
25550
25785
|
}
|
|
25551
|
-
const { version: version2, material } =
|
|
25786
|
+
const { version: version2, material } = parsed2;
|
|
25552
25787
|
if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
|
|
25553
25788
|
throw new Error("exception key file is corrupt: bad version");
|
|
25554
25789
|
}
|
|
@@ -25579,7 +25814,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
25579
25814
|
}
|
|
25580
25815
|
};
|
|
25581
25816
|
function storedKeyVersionFloor(dataDir2) {
|
|
25582
|
-
const file2 =
|
|
25817
|
+
const file2 = join4(dataDir2, DB_FILENAME);
|
|
25583
25818
|
if (!existsSync3(file2)) return 0;
|
|
25584
25819
|
let db;
|
|
25585
25820
|
try {
|
|
@@ -25634,7 +25869,7 @@ function occupantMessage(file2, kind) {
|
|
|
25634
25869
|
function readFingerprintKey(dataDir2) {
|
|
25635
25870
|
let raw;
|
|
25636
25871
|
try {
|
|
25637
|
-
raw =
|
|
25872
|
+
raw = readFileSync3(keyFilePath(dataDir2), "utf8");
|
|
25638
25873
|
} catch (err) {
|
|
25639
25874
|
if (err.code === "ENOENT") return null;
|
|
25640
25875
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -25660,21 +25895,25 @@ function fingerprintValue(key, raw) {
|
|
|
25660
25895
|
import { renameSync as renameSync3 } from "fs";
|
|
25661
25896
|
import { mkdir } from "fs/promises";
|
|
25662
25897
|
import { homedir } from "os";
|
|
25663
|
-
import { join as
|
|
25898
|
+
import { join as join5 } from "path";
|
|
25664
25899
|
function defaultDataDir() {
|
|
25665
|
-
return
|
|
25900
|
+
return join5(homedir(), ".aka");
|
|
25666
25901
|
}
|
|
25667
25902
|
function settingsDir(base = defaultDataDir()) {
|
|
25668
|
-
return
|
|
25903
|
+
return join5(base, "settings");
|
|
25669
25904
|
}
|
|
25670
25905
|
function dataDir(base = defaultDataDir()) {
|
|
25671
|
-
return
|
|
25906
|
+
return join5(base, "data");
|
|
25672
25907
|
}
|
|
25673
25908
|
function dbPath(base = defaultDataDir()) {
|
|
25674
|
-
return
|
|
25909
|
+
return join5(dataDir(base), "aka.db");
|
|
25675
25910
|
}
|
|
25676
25911
|
function keysDir(base = defaultDataDir()) {
|
|
25677
|
-
return
|
|
25912
|
+
return join5(base, "keys");
|
|
25913
|
+
}
|
|
25914
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
25915
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
25916
|
+
tightenDir(dir);
|
|
25678
25917
|
}
|
|
25679
25918
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
25680
25919
|
ensureDataDirSync(dir);
|
|
@@ -25687,8 +25926,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25687
25926
|
for (const { name, dest } of moves) {
|
|
25688
25927
|
try {
|
|
25689
25928
|
ensureDataDirSync(dest);
|
|
25690
|
-
const moved =
|
|
25691
|
-
renameSync3(
|
|
25929
|
+
const moved = join5(dest, name);
|
|
25930
|
+
renameSync3(join5(base, name), moved);
|
|
25692
25931
|
tightenFile(moved);
|
|
25693
25932
|
} catch {
|
|
25694
25933
|
}
|
|
@@ -25696,7 +25935,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25696
25935
|
}
|
|
25697
25936
|
|
|
25698
25937
|
// ../../packages/persistence/src/managed-settings.ts
|
|
25699
|
-
import { readFileSync as
|
|
25938
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
25700
25939
|
import { posix, win32 } from "path";
|
|
25701
25940
|
function managedSettingsPaths(platform2 = process.platform) {
|
|
25702
25941
|
if (platform2 === "darwin") {
|
|
@@ -25714,14 +25953,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
|
|
|
25714
25953
|
for (const path of paths) {
|
|
25715
25954
|
let text;
|
|
25716
25955
|
try {
|
|
25717
|
-
text =
|
|
25956
|
+
text = readFileSync4(path, "utf8");
|
|
25718
25957
|
} catch {
|
|
25719
25958
|
continue;
|
|
25720
25959
|
}
|
|
25721
25960
|
const record2 = parseJsonObject(text);
|
|
25722
25961
|
if (!record2) continue;
|
|
25723
|
-
const
|
|
25724
|
-
if (
|
|
25962
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
25963
|
+
if (parsed2.success) return parsed2.data;
|
|
25725
25964
|
}
|
|
25726
25965
|
return null;
|
|
25727
25966
|
}
|
|
@@ -25761,14 +26000,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
|
|
|
25761
26000
|
}
|
|
25762
26001
|
|
|
25763
26002
|
// ../../packages/persistence/src/settings.ts
|
|
25764
|
-
import { readFileSync as
|
|
25765
|
-
import { join as
|
|
26003
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
26004
|
+
import { join as join6 } from "path";
|
|
25766
26005
|
var SETTINGS_FILENAME = "settings.json";
|
|
25767
26006
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
25768
26007
|
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
25769
26008
|
}
|
|
25770
26009
|
function readUserSettings(base) {
|
|
25771
|
-
const record2 = readJson(
|
|
26010
|
+
const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
|
|
25772
26011
|
if (!record2) return defaultWorkspaceSettings();
|
|
25773
26012
|
try {
|
|
25774
26013
|
return WorkspaceSettings.parse(record2);
|
|
@@ -25779,13 +26018,64 @@ function readUserSettings(base) {
|
|
|
25779
26018
|
function readJson(file2) {
|
|
25780
26019
|
let text;
|
|
25781
26020
|
try {
|
|
25782
|
-
text =
|
|
26021
|
+
text = readFileSync5(file2, "utf8");
|
|
25783
26022
|
} catch {
|
|
25784
26023
|
return null;
|
|
25785
26024
|
}
|
|
25786
26025
|
return parseJsonObject(text) ?? null;
|
|
25787
26026
|
}
|
|
25788
26027
|
|
|
26028
|
+
// ../../packages/persistence/src/store-symlinks.ts
|
|
26029
|
+
import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
26030
|
+
import { dirname as dirname2, join as join7, resolve } from "path";
|
|
26031
|
+
var STORE_DB = "the store database (including the prompt corpus)";
|
|
26032
|
+
var STORE_SETTINGS = "your settings file";
|
|
26033
|
+
function storeContents(home) {
|
|
26034
|
+
return /* @__PURE__ */ new Map([
|
|
26035
|
+
[home, "the store (including the prompt corpus in aka.db)"],
|
|
26036
|
+
[settingsDir(home), STORE_SETTINGS],
|
|
26037
|
+
[dataDir(home), STORE_DB],
|
|
26038
|
+
[keysDir(home), "the vault key"],
|
|
26039
|
+
[join7(settingsDir(home), "settings.json"), STORE_SETTINGS],
|
|
26040
|
+
[dbPath(home), STORE_DB]
|
|
26041
|
+
]);
|
|
26042
|
+
}
|
|
26043
|
+
function symlinkedStorePaths(home, platform2 = process.platform) {
|
|
26044
|
+
return [...storeContents(home)].flatMap(([path, holds]) => {
|
|
26045
|
+
try {
|
|
26046
|
+
if (!lstatSync3(path).isSymbolicLink()) return [];
|
|
26047
|
+
return [
|
|
26048
|
+
{
|
|
26049
|
+
path,
|
|
26050
|
+
target: linkTarget(path),
|
|
26051
|
+
holds,
|
|
26052
|
+
// existsSync follows the link, so a target that is gone reads as
|
|
26053
|
+
// absent here while lstat above still sees the link itself.
|
|
26054
|
+
missing: !existsSync4(path),
|
|
26055
|
+
mode: targetMode(path, platform2)
|
|
26056
|
+
}
|
|
26057
|
+
];
|
|
26058
|
+
} catch {
|
|
26059
|
+
return [];
|
|
26060
|
+
}
|
|
26061
|
+
});
|
|
26062
|
+
}
|
|
26063
|
+
function linkTarget(path) {
|
|
26064
|
+
try {
|
|
26065
|
+
return realpathSync(path);
|
|
26066
|
+
} catch {
|
|
26067
|
+
return resolve(dirname2(path), readlinkSync(path));
|
|
26068
|
+
}
|
|
26069
|
+
}
|
|
26070
|
+
function targetMode(path, platform2) {
|
|
26071
|
+
if (platform2 === "win32") return void 0;
|
|
26072
|
+
try {
|
|
26073
|
+
return statSync4(path).mode & 511;
|
|
26074
|
+
} catch {
|
|
26075
|
+
return void 0;
|
|
26076
|
+
}
|
|
26077
|
+
}
|
|
26078
|
+
|
|
25789
26079
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
25790
26080
|
import {
|
|
25791
26081
|
createCipheriv,
|
|
@@ -25897,8 +26187,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
25897
26187
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25898
26188
|
import { execFileSync } from "child_process";
|
|
25899
26189
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
25900
|
-
import { chmodSync as
|
|
25901
|
-
import { join as
|
|
26190
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
26191
|
+
import { join as join8 } from "path";
|
|
25902
26192
|
var VAULT_OCCUPANT_REASON = {
|
|
25903
26193
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25904
26194
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -25917,11 +26207,11 @@ var KEY_MATERIAL_BYTES2 = 32;
|
|
|
25917
26207
|
var KEYCHAIN_SERVICE = "aka-vault";
|
|
25918
26208
|
var KEYCHAIN_ACCOUNT = "keyring";
|
|
25919
26209
|
function parseKeyring(raw) {
|
|
25920
|
-
const
|
|
25921
|
-
if (typeof
|
|
26210
|
+
const parsed2 = JSON.parse(raw);
|
|
26211
|
+
if (typeof parsed2 !== "object" || parsed2 === null) {
|
|
25922
26212
|
throw new Error("vault key file is corrupt: not a JSON object");
|
|
25923
26213
|
}
|
|
25924
|
-
const { current, keys } =
|
|
26214
|
+
const { current, keys } = parsed2;
|
|
25925
26215
|
if (typeof current !== "number" || !Number.isInteger(current) || current < 1) {
|
|
25926
26216
|
throw new Error("vault key file is corrupt: bad current version");
|
|
25927
26217
|
}
|
|
@@ -25997,28 +26287,28 @@ function claimRotationLock(lock, owner) {
|
|
|
25997
26287
|
throw asError(err);
|
|
25998
26288
|
}
|
|
25999
26289
|
try {
|
|
26000
|
-
writeFileSync3(
|
|
26290
|
+
writeFileSync3(join8(lock, LOCK_OWNER_FILE), `${owner}
|
|
26001
26291
|
`, { mode: DATA_FILE_MODE });
|
|
26002
26292
|
return true;
|
|
26003
26293
|
} catch (err) {
|
|
26004
|
-
|
|
26294
|
+
rmSync5(lock, { recursive: true, force: true });
|
|
26005
26295
|
throw asError(err);
|
|
26006
26296
|
}
|
|
26007
26297
|
}
|
|
26008
26298
|
function acquireRotationLock(keysDir2) {
|
|
26009
|
-
const lock =
|
|
26299
|
+
const lock = join8(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
26010
26300
|
const owner = randomBytes2(16).toString("hex");
|
|
26011
26301
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
26012
26302
|
let held;
|
|
26013
26303
|
try {
|
|
26014
|
-
held =
|
|
26304
|
+
held = statSync5(lock);
|
|
26015
26305
|
} catch {
|
|
26016
26306
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26017
26307
|
}
|
|
26018
26308
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
26019
26309
|
const aside = `${lock}.stale.${owner}`;
|
|
26020
26310
|
try {
|
|
26021
|
-
const now =
|
|
26311
|
+
const now = statSync5(lock);
|
|
26022
26312
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
26023
26313
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26024
26314
|
}
|
|
@@ -26027,17 +26317,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
26027
26317
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
26028
26318
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
26029
26319
|
}
|
|
26030
|
-
|
|
26320
|
+
rmSync5(aside, { recursive: true, force: true });
|
|
26031
26321
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
26032
26322
|
return { lock, owner };
|
|
26033
26323
|
}
|
|
26034
26324
|
function releaseRotationLock(lease) {
|
|
26035
26325
|
try {
|
|
26036
|
-
if (
|
|
26326
|
+
if (readFileSync6(join8(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
26037
26327
|
} catch {
|
|
26038
26328
|
return;
|
|
26039
26329
|
}
|
|
26040
|
-
|
|
26330
|
+
rmSync5(lease.lock, { recursive: true, force: true });
|
|
26041
26331
|
}
|
|
26042
26332
|
function withRotationLock(keysDir2, work) {
|
|
26043
26333
|
ensureDataDirSync(keysDir2);
|
|
@@ -26054,7 +26344,7 @@ var FileKeyProvider = class {
|
|
|
26054
26344
|
this.#keysDir = keysDir2;
|
|
26055
26345
|
}
|
|
26056
26346
|
get filePath() {
|
|
26057
|
-
return
|
|
26347
|
+
return join8(this.#keysDir, VAULT_KEY_FILENAME);
|
|
26058
26348
|
}
|
|
26059
26349
|
loadOrCreate() {
|
|
26060
26350
|
return asAsync(() => {
|
|
@@ -26084,7 +26374,7 @@ var FileKeyProvider = class {
|
|
|
26084
26374
|
#read() {
|
|
26085
26375
|
let raw;
|
|
26086
26376
|
try {
|
|
26087
|
-
raw =
|
|
26377
|
+
raw = readFileSync6(this.filePath, "utf8");
|
|
26088
26378
|
} catch (err) {
|
|
26089
26379
|
if (err.code === "ENOENT") return null;
|
|
26090
26380
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -26141,7 +26431,7 @@ var FileKeyProvider = class {
|
|
|
26141
26431
|
};
|
|
26142
26432
|
function tightenFileMode(file2) {
|
|
26143
26433
|
try {
|
|
26144
|
-
|
|
26434
|
+
chmodSync3(file2, DATA_FILE_MODE);
|
|
26145
26435
|
} catch {
|
|
26146
26436
|
}
|
|
26147
26437
|
}
|
|
@@ -26406,25 +26696,25 @@ var SecretVault = class {
|
|
|
26406
26696
|
* model. Every call that gets as far as an identified row writes an audit row.
|
|
26407
26697
|
*/
|
|
26408
26698
|
async detokenize(token, opts) {
|
|
26409
|
-
const
|
|
26410
|
-
if (!
|
|
26699
|
+
const parsed2 = parsePointer(token);
|
|
26700
|
+
if (!parsed2) return UNAVAILABLE;
|
|
26411
26701
|
let signKey;
|
|
26412
26702
|
try {
|
|
26413
|
-
const epoch = await this.#keys.materialFor(
|
|
26703
|
+
const epoch = await this.#keys.materialFor(parsed2.keyVersion);
|
|
26414
26704
|
signKey = deriveSubkeys(epoch.material).sign;
|
|
26415
26705
|
} catch {
|
|
26416
26706
|
return UNAVAILABLE;
|
|
26417
26707
|
}
|
|
26418
|
-
if (!verifyPointerTag(signKey,
|
|
26708
|
+
if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
|
|
26419
26709
|
return UNAVAILABLE;
|
|
26420
26710
|
}
|
|
26421
|
-
const pointerId = base32Encode(
|
|
26711
|
+
const pointerId = base32Encode(parsed2.pointerId);
|
|
26422
26712
|
const row = this.#repo.byPointerId(pointerId);
|
|
26423
26713
|
if (!row) {
|
|
26424
26714
|
this.#audit(pointerId, opts, "unavailable");
|
|
26425
26715
|
return UNAVAILABLE;
|
|
26426
26716
|
}
|
|
26427
|
-
if (row.category !==
|
|
26717
|
+
if (row.category !== parsed2.category) return UNAVAILABLE;
|
|
26428
26718
|
if (opts.target === "model") {
|
|
26429
26719
|
const grantId = opts.grantId;
|
|
26430
26720
|
const verify = this.#verifyGrant;
|
|
@@ -26461,7 +26751,7 @@ var SecretVault = class {
|
|
|
26461
26751
|
// moved the epoch past the one this token names, and a format bump may
|
|
26462
26752
|
// have moved the constant past the generation this row was sealed
|
|
26463
26753
|
// under — the AAD follows the row in both cases, never the token.
|
|
26464
|
-
bindingInput(row.keyVersion,
|
|
26754
|
+
bindingInput(row.keyVersion, parsed2.pointerId, row.category, row.formatVersion)
|
|
26465
26755
|
);
|
|
26466
26756
|
} catch {
|
|
26467
26757
|
raw = null;
|
|
@@ -26679,19 +26969,19 @@ var SecretVault = class {
|
|
|
26679
26969
|
// preview. Verifying needs the historical epoch's key, which is why these
|
|
26680
26970
|
// surfaces are async.
|
|
26681
26971
|
async #rowFor(token) {
|
|
26682
|
-
const
|
|
26683
|
-
if (!
|
|
26972
|
+
const parsed2 = parsePointer(token);
|
|
26973
|
+
if (!parsed2) return null;
|
|
26684
26974
|
try {
|
|
26685
|
-
const epoch = await this.#keys.materialFor(
|
|
26975
|
+
const epoch = await this.#keys.materialFor(parsed2.keyVersion);
|
|
26686
26976
|
const signKey = deriveSubkeys(epoch.material).sign;
|
|
26687
|
-
if (!verifyPointerTag(signKey,
|
|
26977
|
+
if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
|
|
26688
26978
|
return null;
|
|
26689
26979
|
}
|
|
26690
26980
|
} catch {
|
|
26691
26981
|
return null;
|
|
26692
26982
|
}
|
|
26693
|
-
const row = this.#repo.byPointerId(base32Encode(
|
|
26694
|
-
if (row?.category !==
|
|
26983
|
+
const row = this.#repo.byPointerId(base32Encode(parsed2.pointerId));
|
|
26984
|
+
if (row?.category !== parsed2.category) return null;
|
|
26695
26985
|
return row;
|
|
26696
26986
|
}
|
|
26697
26987
|
#audit(pointerId, opts, outcome) {
|
|
@@ -26711,19 +27001,66 @@ var SecretVault = class {
|
|
|
26711
27001
|
};
|
|
26712
27002
|
|
|
26713
27003
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
26714
|
-
import { existsSync as
|
|
26715
|
-
import { join as
|
|
27004
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
27005
|
+
import { join as join9 } from "path";
|
|
26716
27006
|
var MARKER = "warn-era-capped";
|
|
26717
27007
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
26718
27008
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
26719
|
-
const marker =
|
|
26720
|
-
if (
|
|
27009
|
+
const marker = join9(dataDir2, MARKER);
|
|
27010
|
+
if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
|
|
26721
27011
|
const capped = db.policies.capCategoryActions();
|
|
26722
27012
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
26723
27013
|
`, { mode: DATA_FILE_MODE });
|
|
26724
27014
|
return { capped };
|
|
26725
27015
|
}
|
|
26726
27016
|
|
|
27017
|
+
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
27018
|
+
var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
27019
|
+
function forwardDropsPath(dataDir2) {
|
|
27020
|
+
return join10(dataDir2, FORWARD_DROPS_FILENAME);
|
|
27021
|
+
}
|
|
27022
|
+
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
27023
|
+
if (count <= 0) return;
|
|
27024
|
+
try {
|
|
27025
|
+
ensureDataDirSync(dataDir2);
|
|
27026
|
+
const previous = readForwardDrops(dataDir2);
|
|
27027
|
+
const next = {
|
|
27028
|
+
droppedForwards: (previous?.droppedForwards ?? 0) + count,
|
|
27029
|
+
lastDropAtMs: nowMs
|
|
27030
|
+
};
|
|
27031
|
+
writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
|
|
27032
|
+
`);
|
|
27033
|
+
} catch {
|
|
27034
|
+
}
|
|
27035
|
+
}
|
|
27036
|
+
function readForwardDrops(dataDir2) {
|
|
27037
|
+
try {
|
|
27038
|
+
const parsed2 = JSON.parse(readFileSync7(forwardDropsPath(dataDir2), "utf8"));
|
|
27039
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
27040
|
+
const record2 = parsed2;
|
|
27041
|
+
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
27042
|
+
return null;
|
|
27043
|
+
}
|
|
27044
|
+
if (record2.droppedForwards <= 0) return null;
|
|
27045
|
+
if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
|
|
27046
|
+
return null;
|
|
27047
|
+
}
|
|
27048
|
+
return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
|
|
27049
|
+
} catch {
|
|
27050
|
+
return null;
|
|
27051
|
+
}
|
|
27052
|
+
}
|
|
27053
|
+
|
|
27054
|
+
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
27055
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
27056
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
27057
|
+
import { readFile, rename, writeFile } from "fs/promises";
|
|
27058
|
+
import { join as join18 } from "path";
|
|
27059
|
+
|
|
27060
|
+
// ../../packages/plugin-sdk/src/config.ts
|
|
27061
|
+
import { existsSync as existsSync6 } from "fs";
|
|
27062
|
+
import { join as join11 } from "path";
|
|
27063
|
+
|
|
26727
27064
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
26728
27065
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
26729
27066
|
var booleanish = external_exports.string().optional().transform((v) => {
|
|
@@ -26758,8 +27095,8 @@ function hostOf(url2) {
|
|
|
26758
27095
|
}
|
|
26759
27096
|
}
|
|
26760
27097
|
function resolveProvider() {
|
|
26761
|
-
const
|
|
26762
|
-
const env =
|
|
27098
|
+
const parsed2 = ProviderEnvSchema.safeParse(process.env);
|
|
27099
|
+
const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
|
|
26763
27100
|
if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
|
|
26764
27101
|
if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
|
|
26765
27102
|
const baseUrl = env.ANTHROPIC_BASE_URL;
|
|
@@ -26776,8 +27113,8 @@ function resolveProvider() {
|
|
|
26776
27113
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
26777
27114
|
try {
|
|
26778
27115
|
ensureLayoutDirSync(base);
|
|
26779
|
-
const settingsFile =
|
|
26780
|
-
if (
|
|
27116
|
+
const settingsFile = join11(settingsDir(base), "settings.json");
|
|
27117
|
+
if (existsSync6(settingsFile)) tightenFile(settingsFile);
|
|
26781
27118
|
} catch {
|
|
26782
27119
|
}
|
|
26783
27120
|
migrateLegacyLayout(base);
|
|
@@ -26800,9 +27137,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
26800
27137
|
}
|
|
26801
27138
|
|
|
26802
27139
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
26803
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
27140
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
26804
27141
|
import { homedir as homedir2 } from "os";
|
|
26805
|
-
import { basename as basename3, join as
|
|
27142
|
+
import { basename as basename3, join as join13 } from "path";
|
|
26806
27143
|
|
|
26807
27144
|
// ../../packages/detections/src/egress/registry.ts
|
|
26808
27145
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -28579,10 +28916,10 @@ var localhost_ref_default = {
|
|
|
28579
28916
|
severity: "low",
|
|
28580
28917
|
matcher: {
|
|
28581
28918
|
type: "regex",
|
|
28582
|
-
pattern: "
|
|
28919
|
+
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_])",
|
|
28583
28920
|
flags: "g"
|
|
28584
28921
|
},
|
|
28585
|
-
examples: ["localhost", "127.0.0.1"]
|
|
28922
|
+
examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
|
|
28586
28923
|
};
|
|
28587
28924
|
|
|
28588
28925
|
// ../../rules/core-code-context/stack-trace.json
|
|
@@ -29894,8 +30231,8 @@ function uniqueRuleIds(findings) {
|
|
|
29894
30231
|
}
|
|
29895
30232
|
|
|
29896
30233
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
29897
|
-
import { existsSync as
|
|
29898
|
-
import { basename as basename2, dirname as
|
|
30234
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
30235
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join12, sep as sep2 } from "path";
|
|
29899
30236
|
function resolveRepo(cwd) {
|
|
29900
30237
|
try {
|
|
29901
30238
|
const root = findGitRoot(cwd);
|
|
@@ -29910,36 +30247,36 @@ function resolveRepo(cwd) {
|
|
|
29910
30247
|
function findGitRoot(start) {
|
|
29911
30248
|
let dir = start;
|
|
29912
30249
|
for (; ; ) {
|
|
29913
|
-
if (
|
|
29914
|
-
const parent =
|
|
30250
|
+
if (existsSync7(join12(dir, ".git"))) return dir;
|
|
30251
|
+
const parent = dirname3(dir);
|
|
29915
30252
|
if (parent === dir) return void 0;
|
|
29916
30253
|
dir = parent;
|
|
29917
30254
|
}
|
|
29918
30255
|
}
|
|
29919
30256
|
function resolveGitContext(root) {
|
|
29920
|
-
const dotGit =
|
|
30257
|
+
const dotGit = join12(root, ".git");
|
|
29921
30258
|
try {
|
|
29922
|
-
if (
|
|
29923
|
-
return { configPath:
|
|
30259
|
+
if (statSync6(dotGit).isDirectory()) {
|
|
30260
|
+
return { configPath: join12(dotGit, "config"), headRoot: root };
|
|
29924
30261
|
}
|
|
29925
30262
|
} catch {
|
|
29926
30263
|
return void 0;
|
|
29927
30264
|
}
|
|
29928
30265
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
29929
30266
|
if (!target) return void 0;
|
|
29930
|
-
const gitdir = isAbsolute(target) ? target :
|
|
29931
|
-
if (
|
|
29932
|
-
return { configPath:
|
|
30267
|
+
const gitdir = isAbsolute(target) ? target : join12(root, target);
|
|
30268
|
+
if (existsSync7(join12(gitdir, "config"))) {
|
|
30269
|
+
return { configPath: join12(gitdir, "config"), headRoot: root };
|
|
29933
30270
|
}
|
|
29934
|
-
const commonRaw = safeRead(
|
|
30271
|
+
const commonRaw = safeRead(join12(gitdir, "commondir"))?.trim();
|
|
29935
30272
|
if (!commonRaw) return void 0;
|
|
29936
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
29937
|
-
const headRoot = basename2(commonGitDir) === ".git" ?
|
|
29938
|
-
return { configPath:
|
|
30273
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join12(gitdir, commonRaw);
|
|
30274
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
|
|
30275
|
+
return { configPath: join12(commonGitDir, "config"), headRoot };
|
|
29939
30276
|
}
|
|
29940
30277
|
function safeRead(path) {
|
|
29941
30278
|
try {
|
|
29942
|
-
return
|
|
30279
|
+
return readFileSync8(path, "utf8");
|
|
29943
30280
|
} catch {
|
|
29944
30281
|
return void 0;
|
|
29945
30282
|
}
|
|
@@ -30000,7 +30337,7 @@ function buildIngestEvent(input) {
|
|
|
30000
30337
|
}
|
|
30001
30338
|
|
|
30002
30339
|
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
30003
|
-
import { existsSync as
|
|
30340
|
+
import { existsSync as existsSync8 } from "fs";
|
|
30004
30341
|
import { fileURLToPath } from "url";
|
|
30005
30342
|
import { Worker } from "worker_threads";
|
|
30006
30343
|
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
@@ -30014,7 +30351,7 @@ function resolveWorkerUrl() {
|
|
|
30014
30351
|
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
30015
30352
|
const candidate = new URL(name, import.meta.url);
|
|
30016
30353
|
try {
|
|
30017
|
-
if (
|
|
30354
|
+
if (existsSync8(fileURLToPath(candidate))) {
|
|
30018
30355
|
resolvedWorkerUrl = candidate;
|
|
30019
30356
|
return candidate;
|
|
30020
30357
|
}
|
|
@@ -30199,8 +30536,8 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30199
30536
|
}
|
|
30200
30537
|
function enqueue(spec) {
|
|
30201
30538
|
const next = chain.then(
|
|
30202
|
-
() => new Promise((
|
|
30203
|
-
spec(
|
|
30539
|
+
() => new Promise((resolve2) => {
|
|
30540
|
+
spec(resolve2);
|
|
30204
30541
|
})
|
|
30205
30542
|
);
|
|
30206
30543
|
chain = next.then(
|
|
@@ -30211,7 +30548,7 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30211
30548
|
}
|
|
30212
30549
|
return {
|
|
30213
30550
|
scan(text, context, scanOpts) {
|
|
30214
|
-
return enqueue((
|
|
30551
|
+
return enqueue((resolve2) => {
|
|
30215
30552
|
runOne(
|
|
30216
30553
|
{
|
|
30217
30554
|
budgetMs,
|
|
@@ -30224,23 +30561,23 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30224
30561
|
}),
|
|
30225
30562
|
reply: (message) => {
|
|
30226
30563
|
if (message.kind !== "result") return false;
|
|
30227
|
-
|
|
30564
|
+
resolve2({ status: "ok", findings: message.findings });
|
|
30228
30565
|
return true;
|
|
30229
30566
|
}
|
|
30230
30567
|
},
|
|
30231
|
-
|
|
30568
|
+
resolve2
|
|
30232
30569
|
);
|
|
30233
30570
|
});
|
|
30234
30571
|
},
|
|
30235
30572
|
probe(rule) {
|
|
30236
|
-
return enqueue((
|
|
30573
|
+
return enqueue((resolve2) => {
|
|
30237
30574
|
runOne(
|
|
30238
30575
|
{
|
|
30239
30576
|
budgetMs: probeBudgetMs,
|
|
30240
30577
|
build: (id) => ({ kind: "probe", id, rule }),
|
|
30241
30578
|
reply: (message) => {
|
|
30242
30579
|
if (message.kind !== "probed") return false;
|
|
30243
|
-
|
|
30580
|
+
resolve2({
|
|
30244
30581
|
status: "ok",
|
|
30245
30582
|
verdict: message.verdict,
|
|
30246
30583
|
worstMs: message.worstMs,
|
|
@@ -30249,7 +30586,7 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30249
30586
|
return true;
|
|
30250
30587
|
}
|
|
30251
30588
|
},
|
|
30252
|
-
|
|
30589
|
+
resolve2
|
|
30253
30590
|
);
|
|
30254
30591
|
});
|
|
30255
30592
|
},
|
|
@@ -30479,23 +30816,23 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
30479
30816
|
|
|
30480
30817
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
30481
30818
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
30482
|
-
import { readFileSync as
|
|
30483
|
-
import { join as
|
|
30819
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
30820
|
+
import { join as join14 } from "path";
|
|
30484
30821
|
|
|
30485
30822
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
30486
30823
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
30487
30824
|
|
|
30488
30825
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
30489
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
30490
|
-
import { join as
|
|
30826
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
30827
|
+
import { join as join15 } from "path";
|
|
30491
30828
|
|
|
30492
30829
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
30493
|
-
import { readdirSync as readdirSync3, realpathSync as
|
|
30494
|
-
import { basename as basename4, dirname as
|
|
30830
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
30831
|
+
import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
|
|
30495
30832
|
|
|
30496
30833
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
30497
|
-
import { existsSync as
|
|
30498
|
-
import { basename as basename5, join as
|
|
30834
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
30835
|
+
import { basename as basename5, join as join16 } from "path";
|
|
30499
30836
|
|
|
30500
30837
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30501
30838
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -30866,8 +31203,8 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30866
31203
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
30867
31204
|
|
|
30868
31205
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
30869
|
-
import { mkdirSync as mkdirSync3, statSync as
|
|
30870
|
-
import { join as
|
|
31206
|
+
import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
31207
|
+
import { join as join17 } from "path";
|
|
30871
31208
|
|
|
30872
31209
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
30873
31210
|
function redactedPlaceholder(category) {
|
|
@@ -31169,54 +31506,1181 @@ var UNOPENABLE_VAULT = {
|
|
|
31169
31506
|
resolvePointerIdentity: () => Promise.resolve(null)
|
|
31170
31507
|
};
|
|
31171
31508
|
|
|
31172
|
-
// ../../packages/plugin-runtime/src/
|
|
31173
|
-
|
|
31174
|
-
|
|
31175
|
-
|
|
31176
|
-
|
|
31509
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
31510
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
31511
|
+
function withTimeout(promise2, ms) {
|
|
31512
|
+
let timer;
|
|
31513
|
+
const timeout = new Promise((_, reject) => {
|
|
31514
|
+
timer = setTimeout(() => {
|
|
31515
|
+
reject(new Error("attached gateway request timed out"));
|
|
31516
|
+
}, ms);
|
|
31517
|
+
});
|
|
31518
|
+
promise2.catch(() => void 0);
|
|
31519
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
31520
|
+
clearTimeout(timer);
|
|
31521
|
+
});
|
|
31522
|
+
}
|
|
31177
31523
|
|
|
31178
|
-
// ../../packages/plugin-runtime/src/
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
31182
|
-
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31190
|
-
|
|
31191
|
-
|
|
31192
|
-
|
|
31524
|
+
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
31525
|
+
function isInvalidRequest(err) {
|
|
31526
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
31527
|
+
}
|
|
31528
|
+
var FORWARD_BUDGET_MS = 1500;
|
|
31529
|
+
var DECISION_PATH_BUDGET_MS = 800;
|
|
31530
|
+
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
31531
|
+
var BREAKER_COOLDOWN_MS = 3e4;
|
|
31532
|
+
var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
|
|
31533
|
+
var FAILURES = /* @__PURE__ */ new Set([
|
|
31534
|
+
"unauthorized",
|
|
31535
|
+
"forbidden",
|
|
31536
|
+
"unreachable"
|
|
31537
|
+
]);
|
|
31538
|
+
var FORWARD_STATE_FILENAME = "attached-state.json";
|
|
31539
|
+
var STATE_FILENAME = FORWARD_STATE_FILENAME;
|
|
31540
|
+
function parseBreakerState(raw, nowMs) {
|
|
31541
|
+
try {
|
|
31542
|
+
const parsed2 = JSON.parse(raw);
|
|
31543
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
31544
|
+
const record2 = parsed2;
|
|
31545
|
+
const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
|
|
31546
|
+
const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
|
|
31547
|
+
const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
|
|
31548
|
+
return { consecutiveFailures: failures, openedAtMs, lastFailure };
|
|
31549
|
+
} catch {
|
|
31550
|
+
return null;
|
|
31193
31551
|
}
|
|
31194
|
-
|
|
31195
|
-
|
|
31552
|
+
}
|
|
31553
|
+
function createForwardPolicy(deps) {
|
|
31554
|
+
const now = deps.now ?? (() => Date.now());
|
|
31555
|
+
const file2 = join18(deps.dir, STATE_FILENAME);
|
|
31556
|
+
let state = null;
|
|
31557
|
+
let loading = null;
|
|
31558
|
+
async function readState() {
|
|
31559
|
+
let raw;
|
|
31560
|
+
try {
|
|
31561
|
+
raw = await readFile(file2, "utf8");
|
|
31562
|
+
} catch {
|
|
31563
|
+
return { ...CLOSED };
|
|
31564
|
+
}
|
|
31565
|
+
return parseBreakerState(raw, now()) ?? { ...CLOSED };
|
|
31196
31566
|
}
|
|
31197
|
-
|
|
31198
|
-
|
|
31199
|
-
|
|
31567
|
+
async function load() {
|
|
31568
|
+
if (state !== null) return state;
|
|
31569
|
+
loading ??= readState().then((loaded) => {
|
|
31570
|
+
state = loaded;
|
|
31571
|
+
loading = null;
|
|
31572
|
+
return loaded;
|
|
31573
|
+
});
|
|
31574
|
+
return loading;
|
|
31200
31575
|
}
|
|
31201
|
-
|
|
31202
|
-
|
|
31203
|
-
|
|
31204
|
-
|
|
31205
|
-
|
|
31206
|
-
|
|
31207
|
-
|
|
31576
|
+
async function persist(next) {
|
|
31577
|
+
state = next;
|
|
31578
|
+
try {
|
|
31579
|
+
await ensureDataDir(deps.dir);
|
|
31580
|
+
const tmp = `${file2}.${randomUUID15()}.tmp`;
|
|
31581
|
+
await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
31582
|
+
await rename(tmp, file2);
|
|
31583
|
+
} catch {
|
|
31584
|
+
}
|
|
31208
31585
|
}
|
|
31209
|
-
|
|
31210
|
-
|
|
31211
|
-
|
|
31212
|
-
|
|
31586
|
+
return {
|
|
31587
|
+
async run(op, opts) {
|
|
31588
|
+
const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
|
|
31589
|
+
let current;
|
|
31590
|
+
try {
|
|
31591
|
+
current = await load();
|
|
31592
|
+
} catch {
|
|
31593
|
+
current = { ...CLOSED };
|
|
31594
|
+
}
|
|
31595
|
+
const at = now();
|
|
31596
|
+
if (current.openedAtMs !== null) {
|
|
31597
|
+
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
31598
|
+
return { ok: false, reason: "breaker-open" };
|
|
31599
|
+
}
|
|
31600
|
+
await persist({
|
|
31601
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
31602
|
+
openedAtMs: at,
|
|
31603
|
+
lastFailure: current.lastFailure
|
|
31604
|
+
});
|
|
31605
|
+
}
|
|
31606
|
+
try {
|
|
31607
|
+
const value = await withTimeout(op(), budget);
|
|
31608
|
+
if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
|
|
31609
|
+
await persist({ ...CLOSED });
|
|
31610
|
+
}
|
|
31611
|
+
return { ok: true, value };
|
|
31612
|
+
} catch (err) {
|
|
31613
|
+
if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
|
|
31614
|
+
const reason = classifyFailure(err);
|
|
31615
|
+
const failures = current.consecutiveFailures + 1;
|
|
31616
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
31617
|
+
await persist({
|
|
31618
|
+
consecutiveFailures: failures,
|
|
31619
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
31620
|
+
lastFailure: reason
|
|
31621
|
+
});
|
|
31622
|
+
return { ok: false, reason };
|
|
31623
|
+
}
|
|
31624
|
+
}
|
|
31625
|
+
};
|
|
31626
|
+
}
|
|
31627
|
+
|
|
31628
|
+
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
31629
|
+
var ACTION_STRENGTH = {
|
|
31630
|
+
allow: 0,
|
|
31631
|
+
log: 1,
|
|
31632
|
+
warn: 2,
|
|
31633
|
+
redact: 3,
|
|
31634
|
+
block: 4
|
|
31635
|
+
};
|
|
31636
|
+
function ruleCategoryMap(wireRules, localRules) {
|
|
31637
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
31638
|
+
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
31639
|
+
for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
|
|
31640
|
+
for (const pack of bundledDetections()) {
|
|
31641
|
+
for (const rule of pack.rules) map2.set(rule.id, rule.category);
|
|
31642
|
+
}
|
|
31643
|
+
return map2;
|
|
31644
|
+
}
|
|
31645
|
+
function strongerOf(a, b) {
|
|
31646
|
+
if (a === null) return b;
|
|
31647
|
+
if (b === null) return a;
|
|
31648
|
+
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
31649
|
+
}
|
|
31650
|
+
function policyKey(policy) {
|
|
31651
|
+
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
31652
|
+
}
|
|
31653
|
+
function floorFor(policy, categoryByRuleId) {
|
|
31654
|
+
const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
|
|
31655
|
+
return category === void 0 ? null : DEFAULT_ACTIONS[category];
|
|
31656
|
+
}
|
|
31657
|
+
function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
31658
|
+
const merged = /* @__PURE__ */ new Map();
|
|
31659
|
+
const disabled = [];
|
|
31660
|
+
const remoteCategoryAction = /* @__PURE__ */ new Map();
|
|
31661
|
+
for (const policy of remotePolicies) {
|
|
31662
|
+
if (!policy.enabled) continue;
|
|
31663
|
+
if (!("category" in policy.target)) continue;
|
|
31664
|
+
if (remoteCategoryAction.has(policy.target.category)) continue;
|
|
31665
|
+
const floor = floorFor(policy, categoryByRuleId);
|
|
31666
|
+
remoteCategoryAction.set(
|
|
31667
|
+
policy.target.category,
|
|
31668
|
+
floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
|
|
31669
|
+
);
|
|
31670
|
+
}
|
|
31671
|
+
for (const policy of localPolicies) {
|
|
31672
|
+
if (!policy.enabled) {
|
|
31673
|
+
disabled.push(policy);
|
|
31674
|
+
continue;
|
|
31675
|
+
}
|
|
31676
|
+
const key = policyKey(policy);
|
|
31677
|
+
if (merged.has(key)) continue;
|
|
31678
|
+
let remoteFloor = null;
|
|
31679
|
+
if ("ruleId" in policy.target) {
|
|
31680
|
+
const category = categoryByRuleId.get(policy.target.ruleId);
|
|
31681
|
+
if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
|
|
31682
|
+
}
|
|
31683
|
+
merged.set(
|
|
31684
|
+
key,
|
|
31685
|
+
remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
|
|
31686
|
+
);
|
|
31687
|
+
}
|
|
31688
|
+
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
31689
|
+
for (const policy of merged.values()) {
|
|
31690
|
+
if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
|
|
31691
|
+
}
|
|
31692
|
+
for (const policy of remotePolicies) {
|
|
31693
|
+
if (!policy.enabled) {
|
|
31694
|
+
disabled.push(policy);
|
|
31695
|
+
continue;
|
|
31696
|
+
}
|
|
31697
|
+
const key = policyKey(policy);
|
|
31698
|
+
const floor = floorFor(policy, categoryByRuleId);
|
|
31699
|
+
let localFloor = null;
|
|
31700
|
+
if ("ruleId" in policy.target) {
|
|
31701
|
+
const category = categoryByRuleId.get(policy.target.ruleId);
|
|
31702
|
+
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
31703
|
+
}
|
|
31704
|
+
const effectiveFloor = strongerOf(floor, localFloor);
|
|
31705
|
+
const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
|
|
31706
|
+
const existing = merged.get(key);
|
|
31707
|
+
if (existing === void 0) {
|
|
31708
|
+
merged.set(key, clamped);
|
|
31709
|
+
continue;
|
|
31710
|
+
}
|
|
31711
|
+
if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
|
|
31712
|
+
merged.set(key, clamped);
|
|
31713
|
+
}
|
|
31714
|
+
}
|
|
31715
|
+
return [...merged.values(), ...disabled];
|
|
31716
|
+
}
|
|
31717
|
+
var AttachedDataGateway = class {
|
|
31718
|
+
constructor(deps) {
|
|
31719
|
+
this.deps = deps;
|
|
31720
|
+
}
|
|
31721
|
+
deps;
|
|
31722
|
+
/**
|
|
31723
|
+
* The control plane's OWN resolution of this session's inventory, captured by
|
|
31724
|
+
* ensureInventory. Null until the first successful forward — and it stays
|
|
31725
|
+
* null for the whole session when the control plane is unreachable, which is fine:
|
|
31726
|
+
* reKeyForForward then leaves the event's ids alone and the control plane resolves
|
|
31727
|
+
* what it can from the descriptors it already has.
|
|
31728
|
+
*/
|
|
31729
|
+
remoteInventory = null;
|
|
31730
|
+
// ---------------------------------------------------------------------
|
|
31731
|
+
// Writes: local first, then forward.
|
|
31732
|
+
// ---------------------------------------------------------------------
|
|
31733
|
+
async recordCapture(record2) {
|
|
31734
|
+
await this.deps.local.recordCapture(record2);
|
|
31735
|
+
await this.deps.forward.run(
|
|
31736
|
+
() => this.deps.client.ingestEvents({
|
|
31737
|
+
events: [record2.event],
|
|
31738
|
+
...record2.dedupe ? { dedupe: record2.dedupe } : {}
|
|
31739
|
+
}),
|
|
31740
|
+
{ decisionPath: true }
|
|
31741
|
+
);
|
|
31742
|
+
}
|
|
31743
|
+
async ensureInventory(ctx) {
|
|
31744
|
+
const resolved = await this.deps.local.ensureInventory(ctx);
|
|
31745
|
+
const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
|
|
31746
|
+
this.remoteInventory = remote.ok ? remote.value : null;
|
|
31747
|
+
const snapshot = await (async () => {
|
|
31748
|
+
try {
|
|
31749
|
+
return await this.deps.posture?.prepare() ?? null;
|
|
31750
|
+
} catch {
|
|
31751
|
+
return null;
|
|
31752
|
+
}
|
|
31753
|
+
})();
|
|
31754
|
+
if (snapshot) {
|
|
31755
|
+
try {
|
|
31756
|
+
await withTimeout(
|
|
31757
|
+
this.deps.posture?.send(snapshot) ?? Promise.resolve(),
|
|
31758
|
+
REQUEST_TIMEOUT_MS
|
|
31759
|
+
);
|
|
31760
|
+
} catch {
|
|
31761
|
+
}
|
|
31762
|
+
}
|
|
31763
|
+
return resolved;
|
|
31764
|
+
}
|
|
31765
|
+
// The id is minted CLIENT-side and stored verbatim: the control plane does NOT
|
|
31766
|
+
// re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
|
|
31767
|
+
// its own scoping columns, so the device and the forwarded copy
|
|
31768
|
+
// share one id space — which is what makes a re-post idempotent at all.
|
|
31769
|
+
//
|
|
31770
|
+
// Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
|
|
31771
|
+
// `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
|
|
31772
|
+
// what makes an attached retry safe: a capture-stubbed session row can still
|
|
31773
|
+
// be HEALED by the authoritative root, while a duplicate non-session event —
|
|
31774
|
+
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
31775
|
+
async recordAuditEvent(event) {
|
|
31776
|
+
await this.deps.local.recordAuditEvent(event);
|
|
31777
|
+
await this.deps.forward.run(
|
|
31778
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
31779
|
+
);
|
|
31780
|
+
}
|
|
31781
|
+
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
31782
|
+
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
31783
|
+
// client method yet) by pre-building the audit event from the natural key.
|
|
31784
|
+
// The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
|
|
31785
|
+
// which would write the event to the local store a second time.
|
|
31786
|
+
async recordLlmCall(input) {
|
|
31787
|
+
await this.deps.local.recordLlmCall(input);
|
|
31788
|
+
await this.deps.forward.run(
|
|
31789
|
+
() => this.deps.client.recordAuditEvent(
|
|
31790
|
+
reKeyForForward(llmAuditEvent(input), this.remoteInventory)
|
|
31791
|
+
)
|
|
31792
|
+
);
|
|
31793
|
+
}
|
|
31794
|
+
/**
|
|
31795
|
+
* Forward one batch, item by item, under ONE aggregate deadline.
|
|
31796
|
+
*
|
|
31797
|
+
* Per-item budgets bound each request and nothing bounded their sum — see
|
|
31798
|
+
* BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
|
|
31799
|
+
* rather than sent: the local write has already succeeded, so every caller
|
|
31800
|
+
* has a correct result to return, and a drop is the outcome this path is
|
|
31801
|
+
* built to accept (G8) where a blown hook timeout is not.
|
|
31802
|
+
*
|
|
31803
|
+
* Serial rather than concurrent on purpose. Firing N requests at once would
|
|
31804
|
+
* trade a latency problem for a burst the plane's own per-key rate limiting
|
|
31805
|
+
* would answer with the refusals the breaker then counts.
|
|
31806
|
+
*
|
|
31807
|
+
* WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
|
|
31808
|
+
* `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
|
|
31809
|
+
* lets status call the forward unhealthy; this path returns BEFORE `run` is
|
|
31810
|
+
* reached, so without the tally in `forward-drops.ts` a slow-but-answering
|
|
31811
|
+
* plane produces no failures, keeps the breaker closed, renders a healthy
|
|
31812
|
+
* block, and discards the tail of every batch indefinitely.
|
|
31813
|
+
*/
|
|
31814
|
+
async forwardBatch(inputs, toEvent) {
|
|
31815
|
+
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
31816
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
31817
|
+
const now = Date.now();
|
|
31818
|
+
if (now >= deadline) {
|
|
31819
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
31820
|
+
return;
|
|
31821
|
+
}
|
|
31822
|
+
const input = inputs[i];
|
|
31823
|
+
await this.deps.forward.run(
|
|
31824
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
|
|
31825
|
+
);
|
|
31826
|
+
}
|
|
31827
|
+
}
|
|
31828
|
+
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
31829
|
+
// gateway may write the whole batch in one local transaction, and looping
|
|
31830
|
+
// here would replace that with N separate local writes.
|
|
31831
|
+
async recordLlmCalls(inputs) {
|
|
31832
|
+
await this.deps.local.recordLlmCalls(inputs);
|
|
31833
|
+
await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
|
|
31834
|
+
}
|
|
31835
|
+
// `input.inspections` (secrets detected client-side in the tool's masked
|
|
31836
|
+
// target) ride along on the request's `inspections` field — the control plane
|
|
31837
|
+
// persists each as an inspection_findings row linked to this audit event
|
|
31838
|
+
// (see RecordAuditEventRequest in @akasecurity/schema). The masked
|
|
31839
|
+
// `target` already rides `input.attributes`, so no raw secret leaks either
|
|
31840
|
+
// way — this only stops the FINDING row itself from being dropped.
|
|
31841
|
+
async recordToolCalls(inputs) {
|
|
31842
|
+
await this.deps.local.recordToolCalls(inputs);
|
|
31843
|
+
await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
|
|
31844
|
+
}
|
|
31845
|
+
// Forwarded as a `config_scan` audit event: there is no dedicated
|
|
31846
|
+
// config-scan ingest endpoint, and the audit-event door is the one the
|
|
31847
|
+
// control plane already opens for client-minted, idempotent records.
|
|
31848
|
+
//
|
|
31849
|
+
// ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
|
|
31850
|
+
// re-derive the rest. A `ConfigScanRecord` is four things committed together
|
|
31851
|
+
// locally — the inventory `items`, this audit event, and the posture
|
|
31852
|
+
// `definitions`/`findings` that reference it — and three of them stay on the
|
|
31853
|
+
// device. Say that plainly rather than let the asymmetry with `recordCapture`
|
|
31854
|
+
// read as the same argument: there, findings are omitted BECAUSE the plane
|
|
31855
|
+
// re-derives them from `Event.content`; here there is no content to re-derive
|
|
31856
|
+
// from, so what is omitted is simply not sent.
|
|
31857
|
+
//
|
|
31858
|
+
// That is the wire contract as it stands rather than an oversight to patch
|
|
31859
|
+
// here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
|
|
31860
|
+
// is documented as tool-call findings — widening it to carry config-scan
|
|
31861
|
+
// findings is an egress change (a posture finding's `maskedMatch` holds the
|
|
31862
|
+
// matched command) and a decision about what an attached deployment is
|
|
31863
|
+
// entitled to, not a bug fix. An attached machine's config posture therefore
|
|
31864
|
+
// reaches the plane as the event only; the dashboard's own view of it is the
|
|
31865
|
+
// local store.
|
|
31866
|
+
async recordConfigScan(record2) {
|
|
31867
|
+
await this.deps.local.recordConfigScan(record2);
|
|
31868
|
+
await this.deps.forward.run(
|
|
31869
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
31870
|
+
);
|
|
31871
|
+
}
|
|
31872
|
+
async recordBlockedDetection(entry) {
|
|
31873
|
+
return this.deps.local.recordBlockedDetection(entry);
|
|
31874
|
+
}
|
|
31875
|
+
/**
|
|
31876
|
+
* LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
|
|
31877
|
+
* with no egress ingest endpoint, so there is nothing to forward to; adding a
|
|
31878
|
+
* forward here would be inventing a wire contract that does not exist. The
|
|
31879
|
+
* local write is the whole operation, and its summary is the real one — the
|
|
31880
|
+
* scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
|
|
31881
|
+
* returning the inner gateway's result keeps the retry semantics honest.
|
|
31882
|
+
*/
|
|
31883
|
+
async recordProjectEgress(input) {
|
|
31884
|
+
return this.deps.local.recordProjectEgress(input);
|
|
31885
|
+
}
|
|
31886
|
+
// ---------------------------------------------------------------------
|
|
31887
|
+
// Reads and device-local ledgers: pure delegation.
|
|
31888
|
+
// ---------------------------------------------------------------------
|
|
31889
|
+
async configInventoryReport() {
|
|
31890
|
+
return this.deps.local.configInventoryReport();
|
|
31891
|
+
}
|
|
31892
|
+
async readSessionProvider(sessionId) {
|
|
31893
|
+
return this.deps.local.readSessionProvider(sessionId);
|
|
31894
|
+
}
|
|
31895
|
+
async facets() {
|
|
31896
|
+
return this.deps.local.facets();
|
|
31897
|
+
}
|
|
31898
|
+
/**
|
|
31899
|
+
* Delegated UNMODIFIED — including its refusals.
|
|
31900
|
+
*
|
|
31901
|
+
* This is a fail-secure boundary: it decides whether an approved exception
|
|
31902
|
+
* lets a blocked action through. Under local-first the local store owns the
|
|
31903
|
+
* exception ledger, so the honest answer is whatever it says; wrapping this
|
|
31904
|
+
* in a fallback (`catch { return true }`, or defaulting on a timeout) would
|
|
31905
|
+
* turn a store error into a granted bypass. If the inner gateway rejects,
|
|
31906
|
+
* this rejects, and the runtime's own handling decides — which is asserted
|
|
31907
|
+
* end-to-end through runtime.capture rather than here.
|
|
31908
|
+
*/
|
|
31909
|
+
async consumeException(id) {
|
|
31910
|
+
return this.deps.local.consumeException(id);
|
|
31911
|
+
}
|
|
31912
|
+
async recentFindings(opts) {
|
|
31913
|
+
return this.deps.local.recentFindings(opts);
|
|
31914
|
+
}
|
|
31915
|
+
async healthSummary() {
|
|
31916
|
+
return this.deps.local.healthSummary();
|
|
31917
|
+
}
|
|
31918
|
+
async activityByDay(days) {
|
|
31919
|
+
return this.deps.local.activityByDay(days);
|
|
31920
|
+
}
|
|
31921
|
+
async tokenReports() {
|
|
31922
|
+
return this.deps.local.tokenReports();
|
|
31923
|
+
}
|
|
31924
|
+
async knownContentHashes() {
|
|
31925
|
+
return this.deps.local.knownContentHashes();
|
|
31926
|
+
}
|
|
31927
|
+
async scanLedger(rulesetHash) {
|
|
31928
|
+
return this.deps.local.scanLedger(rulesetHash);
|
|
31929
|
+
}
|
|
31930
|
+
async recordScanned(entries) {
|
|
31931
|
+
return this.deps.local.recordScanned(entries);
|
|
31932
|
+
}
|
|
31933
|
+
async getRuleProbeVerdict(ruleKey) {
|
|
31934
|
+
return this.deps.local.getRuleProbeVerdict(ruleKey);
|
|
31935
|
+
}
|
|
31936
|
+
async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2) {
|
|
31937
|
+
return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2);
|
|
31938
|
+
}
|
|
31939
|
+
async openAtRestKeysForPath(path) {
|
|
31940
|
+
return this.deps.local.openAtRestKeysForPath(path);
|
|
31941
|
+
}
|
|
31942
|
+
async resolvedAtRestKeysForPath(path) {
|
|
31943
|
+
return this.deps.local.resolvedAtRestKeysForPath(path);
|
|
31944
|
+
}
|
|
31945
|
+
async insertResolution(input) {
|
|
31946
|
+
return this.deps.local.insertResolution(input);
|
|
31947
|
+
}
|
|
31948
|
+
async close() {
|
|
31949
|
+
return this.deps.local.close();
|
|
31950
|
+
}
|
|
31951
|
+
// ---------------------------------------------------------------------
|
|
31952
|
+
// Policy
|
|
31953
|
+
// ---------------------------------------------------------------------
|
|
31954
|
+
async getPolicyBundle() {
|
|
31955
|
+
const local = await this.deps.local.getPolicyBundle();
|
|
31956
|
+
const cached2 = await (async () => {
|
|
31957
|
+
try {
|
|
31958
|
+
return await this.deps.readCachedBundle();
|
|
31959
|
+
} catch {
|
|
31960
|
+
return null;
|
|
31961
|
+
}
|
|
31962
|
+
})();
|
|
31963
|
+
if (cached2 === null) return local;
|
|
31964
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
31965
|
+
for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
|
|
31966
|
+
if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
|
|
31967
|
+
}
|
|
31968
|
+
const rules = [...byRuleId.values()];
|
|
31969
|
+
return {
|
|
31970
|
+
...local,
|
|
31971
|
+
// The remote version identifies the composed bundle for the poller.
|
|
31972
|
+
version: cached2.version,
|
|
31973
|
+
rules,
|
|
31974
|
+
policies: mergeRaiseOnly(
|
|
31975
|
+
local.policies,
|
|
31976
|
+
cached2.policies,
|
|
31977
|
+
ruleCategoryMap(cached2.rules, local.rules)
|
|
31978
|
+
),
|
|
31979
|
+
customKeywords: [...local.customKeywords, ...cached2.customKeywords]
|
|
31980
|
+
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
31981
|
+
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
31982
|
+
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
31983
|
+
// anything able to write policy-cache.json, a kill-switch over the
|
|
31984
|
+
// compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
|
|
31985
|
+
// zero local detection. Spread from `local` above, and deliberately not
|
|
31986
|
+
// re-read from `cached` here.
|
|
31987
|
+
//
|
|
31988
|
+
// THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
|
|
31989
|
+
// and each named here so a reader can tell a decision from an omission:
|
|
31990
|
+
//
|
|
31991
|
+
// `exceptions` — an exception SUPPRESSES a detection, so honoring
|
|
31992
|
+
// one from an unsigned on-disk cache would let
|
|
31993
|
+
// anything able to write that file turn rules off.
|
|
31994
|
+
// Every other field this merge accepts can only
|
|
31995
|
+
// RAISE enforcement; this is the one that cannot,
|
|
31996
|
+
// so it stays local-only until the bundle is
|
|
31997
|
+
// signed. Exceptions remain a device-local ledger.
|
|
31998
|
+
// `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
|
|
31999
|
+
// recoverable, which is a CUSTODY change: it puts
|
|
32000
|
+
// the detected value in the local vault instead of
|
|
32001
|
+
// destroying it. Taking that instruction from the
|
|
32002
|
+
// cache would let a remote party turn one-way
|
|
32003
|
+
// redaction into retention. Dropping it keeps the
|
|
32004
|
+
// one-way behaviour, which the schema itself calls
|
|
32005
|
+
// "the safe direction to default".
|
|
32006
|
+
// `ruleVersions` — remote rules fall back to their own spec version.
|
|
32007
|
+
// Cosmetic rather than protective: it only affects
|
|
32008
|
+
// how a finding is version-attributed, and the two
|
|
32009
|
+
// sides may therefore attribute org rules
|
|
32010
|
+
// differently. Worth carrying once there is a
|
|
32011
|
+
// reader that needs it; nothing reads it today.
|
|
32012
|
+
};
|
|
32013
|
+
}
|
|
32014
|
+
// ---------------------------------------------------------------------
|
|
32015
|
+
// LocalStoreMaintenance — by delegation (D3).
|
|
32016
|
+
//
|
|
32017
|
+
// Implementing these is what actually closes the skipped-local-maintenance
|
|
32018
|
+
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
32019
|
+
// any object carrying all five, so the composite qualifies and SessionStart
|
|
32020
|
+
// runs maintenance on the device's real store.
|
|
32021
|
+
//
|
|
32022
|
+
// ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
32023
|
+
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
32024
|
+
// return value directly; declaring them `async` here would hand those call
|
|
32025
|
+
// sites a Promise and silently break both.
|
|
32026
|
+
// ---------------------------------------------------------------------
|
|
32027
|
+
async sweepTerminalExceptions(retentionMs) {
|
|
32028
|
+
return this.deps.local.sweepTerminalExceptions(retentionMs);
|
|
32029
|
+
}
|
|
32030
|
+
capWarnEraEnforcement(policyMode) {
|
|
32031
|
+
return this.deps.local.capWarnEraEnforcement(policyMode);
|
|
32032
|
+
}
|
|
32033
|
+
async recordProjectFiles(projectId, scan2) {
|
|
32034
|
+
return this.deps.local.recordProjectFiles(projectId, scan2);
|
|
32035
|
+
}
|
|
32036
|
+
async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
|
|
32037
|
+
return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
|
|
32038
|
+
}
|
|
32039
|
+
staleBinaryNotice(currentVersion) {
|
|
32040
|
+
return this.deps.local.staleBinaryNotice(currentVersion);
|
|
32041
|
+
}
|
|
32042
|
+
};
|
|
32043
|
+
function reKeyForForward(event, remote) {
|
|
32044
|
+
if (remote === null) {
|
|
32045
|
+
const stripped = { ...event };
|
|
32046
|
+
delete stripped.hostId;
|
|
32047
|
+
delete stripped.harnessId;
|
|
32048
|
+
delete stripped.sourceProjectId;
|
|
32049
|
+
return stripped;
|
|
32050
|
+
}
|
|
32051
|
+
const rekeyed = { ...event };
|
|
32052
|
+
delete rekeyed.hostId;
|
|
32053
|
+
delete rekeyed.harnessId;
|
|
32054
|
+
delete rekeyed.sourceProjectId;
|
|
32055
|
+
if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
|
|
32056
|
+
if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
|
|
32057
|
+
if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
|
|
32058
|
+
return rekeyed;
|
|
32059
|
+
}
|
|
32060
|
+
var BATCH_FORWARD_BUDGET_MS = 3e3;
|
|
32061
|
+
function llmAuditEvent(input) {
|
|
32062
|
+
return {
|
|
32063
|
+
id: llmCallId(input.sessionId, input.messageId),
|
|
32064
|
+
eventType: "llm_call",
|
|
32065
|
+
startedAt: input.startedAt,
|
|
32066
|
+
parentId: input.parentId,
|
|
32067
|
+
rootSessionId: input.rootSessionId,
|
|
32068
|
+
attributes: input.attributes
|
|
32069
|
+
};
|
|
32070
|
+
}
|
|
32071
|
+
function toolAuditEvent(input) {
|
|
32072
|
+
return {
|
|
32073
|
+
id: toolCallId(input.sessionId, input.toolUseId),
|
|
32074
|
+
eventType: "tool_call",
|
|
32075
|
+
startedAt: input.startedAt,
|
|
32076
|
+
parentId: input.parentId,
|
|
32077
|
+
rootSessionId: input.rootSessionId,
|
|
32078
|
+
attributes: input.attributes,
|
|
32079
|
+
inspections: input.inspections
|
|
32080
|
+
};
|
|
32081
|
+
}
|
|
32082
|
+
|
|
32083
|
+
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
32084
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
32085
|
+
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
32086
|
+
import { join as join19 } from "path";
|
|
32087
|
+
|
|
32088
|
+
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
32089
|
+
import { rename as rename2 } from "fs/promises";
|
|
32090
|
+
var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
32091
|
+
var ATTEMPTS = 5;
|
|
32092
|
+
var delay = (ms) => new Promise((resolve2) => {
|
|
32093
|
+
setTimeout(resolve2, ms);
|
|
32094
|
+
});
|
|
32095
|
+
async function publishByRename(tmp, file2, move = rename2) {
|
|
32096
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
32097
|
+
try {
|
|
32098
|
+
await move(tmp, file2);
|
|
32099
|
+
return;
|
|
32100
|
+
} catch (err) {
|
|
32101
|
+
const code = err.code;
|
|
32102
|
+
if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
|
|
32103
|
+
await delay(attempt * 10);
|
|
32104
|
+
}
|
|
32105
|
+
}
|
|
32106
|
+
}
|
|
32107
|
+
|
|
32108
|
+
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
32109
|
+
function createPolicyStore(dir = dataDir()) {
|
|
32110
|
+
const file2 = join19(dir, "policy-cache.json");
|
|
32111
|
+
async function read() {
|
|
32112
|
+
try {
|
|
32113
|
+
const raw = await readFile2(file2, "utf8");
|
|
32114
|
+
const parsed2 = JSON.parse(raw);
|
|
32115
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
32116
|
+
const record2 = parsed2;
|
|
32117
|
+
const bundle = PolicyBundle.parse(record2.bundle);
|
|
32118
|
+
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
32119
|
+
const etag = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
32120
|
+
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
32121
|
+
} catch {
|
|
32122
|
+
return null;
|
|
32123
|
+
}
|
|
32124
|
+
}
|
|
32125
|
+
async function write(bundle, etag) {
|
|
32126
|
+
await ensureDataDir(dir);
|
|
32127
|
+
const stored = {
|
|
32128
|
+
bundle,
|
|
32129
|
+
fetchedAtMs: Date.now(),
|
|
32130
|
+
...etag === void 0 ? {} : { etag }
|
|
32131
|
+
};
|
|
32132
|
+
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
32133
|
+
try {
|
|
32134
|
+
await writeFile2(tmp, JSON.stringify(stored), {
|
|
32135
|
+
encoding: "utf8",
|
|
32136
|
+
mode: DATA_FILE_MODE,
|
|
32137
|
+
flag: "wx"
|
|
32138
|
+
});
|
|
32139
|
+
await publishByRename(tmp, file2);
|
|
32140
|
+
} catch (err) {
|
|
32141
|
+
await rm(tmp, { force: true }).catch(() => void 0);
|
|
32142
|
+
throw err;
|
|
32143
|
+
}
|
|
32144
|
+
}
|
|
32145
|
+
return { read, write, file: file2 };
|
|
32146
|
+
}
|
|
32147
|
+
|
|
32148
|
+
// ../../packages/remote/src/http.ts
|
|
32149
|
+
import { request as httpRequest } from "http";
|
|
32150
|
+
import { request as httpsRequest } from "https";
|
|
32151
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
32152
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
32153
|
+
var RemoteRequestError = class extends Error {
|
|
32154
|
+
constructor(status) {
|
|
32155
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
32156
|
+
this.status = status;
|
|
32157
|
+
this.name = "RemoteRequestError";
|
|
32158
|
+
}
|
|
32159
|
+
status;
|
|
32160
|
+
};
|
|
32161
|
+
var RemoteRequestInvalid = class extends Error {
|
|
32162
|
+
constructor(route, cause) {
|
|
32163
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
32164
|
+
this.cause = cause;
|
|
32165
|
+
this.name = "RemoteRequestInvalid";
|
|
32166
|
+
}
|
|
32167
|
+
cause;
|
|
32168
|
+
};
|
|
32169
|
+
var RemoteResponseInvalid = class extends Error {
|
|
32170
|
+
constructor(route, detail) {
|
|
32171
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
32172
|
+
this.name = "RemoteResponseInvalid";
|
|
32173
|
+
}
|
|
32174
|
+
};
|
|
32175
|
+
var RemoteTransportError = class extends Error {
|
|
32176
|
+
/**
|
|
32177
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
32178
|
+
* refused.
|
|
32179
|
+
*
|
|
32180
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
32181
|
+
* all. It exists because two paths reject after a status has already been
|
|
32182
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
32183
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
32184
|
+
* outage, which sends the reader to look at their network instead of their
|
|
32185
|
+
* credential.
|
|
32186
|
+
*/
|
|
32187
|
+
constructor(reason, status) {
|
|
32188
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
32189
|
+
this.status = status;
|
|
32190
|
+
this.name = "RemoteTransportError";
|
|
32191
|
+
}
|
|
32192
|
+
status;
|
|
32193
|
+
};
|
|
32194
|
+
async function send(options) {
|
|
32195
|
+
const url2 = new URL(options.url);
|
|
32196
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32197
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
32198
|
+
const requestOptions = {
|
|
32199
|
+
method: options.method,
|
|
32200
|
+
headers: {
|
|
32201
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
32202
|
+
// last they win, and two of the values below are ones no caller may
|
|
32203
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
32204
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
32205
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
32206
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
32207
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
32208
|
+
// conditional GET — is untouched by this order.
|
|
32209
|
+
...options.headers,
|
|
32210
|
+
// The credential. One header, matching what the deployment authenticates
|
|
32211
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
32212
|
+
// it can be logged by an intermediary for no gain.
|
|
32213
|
+
"x-api-key": options.apiKey,
|
|
32214
|
+
accept: "application/json",
|
|
32215
|
+
...options.body === void 0 ? {} : {
|
|
32216
|
+
"content-type": "application/json",
|
|
32217
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
32218
|
+
// character count is truncated by the receiver.
|
|
32219
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
32220
|
+
}
|
|
32221
|
+
}
|
|
32222
|
+
};
|
|
32223
|
+
return new Promise((resolve2, reject) => {
|
|
32224
|
+
let settled = false;
|
|
32225
|
+
const fail = (reason, status) => {
|
|
32226
|
+
if (settled) return;
|
|
32227
|
+
settled = true;
|
|
32228
|
+
reject(new RemoteTransportError(reason, status));
|
|
32229
|
+
};
|
|
32230
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
32231
|
+
const chunks = [];
|
|
32232
|
+
let size = 0;
|
|
32233
|
+
res.on("data", (chunk) => {
|
|
32234
|
+
size += chunk.length;
|
|
32235
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
32236
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
32237
|
+
res.destroy();
|
|
32238
|
+
req.destroy();
|
|
32239
|
+
return;
|
|
32240
|
+
}
|
|
32241
|
+
chunks.push(chunk);
|
|
32242
|
+
});
|
|
32243
|
+
res.on("aborted", () => {
|
|
32244
|
+
fail("the response was aborted", res.statusCode);
|
|
32245
|
+
});
|
|
32246
|
+
res.on("end", () => {
|
|
32247
|
+
if (settled) return;
|
|
32248
|
+
settled = true;
|
|
32249
|
+
resolve2({
|
|
32250
|
+
status: res.statusCode ?? 0,
|
|
32251
|
+
headers: res.headers,
|
|
32252
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
32253
|
+
});
|
|
32254
|
+
});
|
|
32255
|
+
});
|
|
32256
|
+
const deadline = setTimeout(() => {
|
|
32257
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
32258
|
+
req.destroy();
|
|
32259
|
+
}, timeoutMs);
|
|
32260
|
+
deadline.unref();
|
|
32261
|
+
req.on("upgrade", (_res, socket) => {
|
|
32262
|
+
fail("the deployment answered with a protocol upgrade");
|
|
32263
|
+
socket.destroy();
|
|
32264
|
+
});
|
|
32265
|
+
req.on("close", () => {
|
|
32266
|
+
fail("the connection closed before a response was read");
|
|
32267
|
+
clearTimeout(deadline);
|
|
32268
|
+
});
|
|
32269
|
+
req.on("error", (err) => {
|
|
32270
|
+
fail(err.message);
|
|
32271
|
+
});
|
|
32272
|
+
if (options.body !== void 0) req.write(options.body);
|
|
32273
|
+
req.end();
|
|
32274
|
+
});
|
|
32275
|
+
}
|
|
32276
|
+
|
|
32277
|
+
// ../../packages/remote/src/client.ts
|
|
32278
|
+
var ROUTES = {
|
|
32279
|
+
events: "/v1/events",
|
|
32280
|
+
auditEvents: "/v1/audit-events",
|
|
32281
|
+
inventory: "/v1/inventory",
|
|
32282
|
+
storePosture: "/v1/store-posture",
|
|
32283
|
+
policyBundle: "/v1/policy-bundle",
|
|
32284
|
+
whoami: "/v1/plugin/whoami"
|
|
32285
|
+
};
|
|
32286
|
+
function headerValue(response, name) {
|
|
32287
|
+
const raw = response.headers[name];
|
|
32288
|
+
if (raw === void 0) return void 0;
|
|
32289
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
32290
|
+
}
|
|
32291
|
+
function okBody(response) {
|
|
32292
|
+
if (response.status < 200 || response.status >= 300) {
|
|
32293
|
+
throw new RemoteRequestError(response.status);
|
|
32294
|
+
}
|
|
32295
|
+
return response.body;
|
|
32296
|
+
}
|
|
32297
|
+
function parsed(schema, body, route) {
|
|
32298
|
+
let json2;
|
|
32299
|
+
try {
|
|
32300
|
+
json2 = JSON.parse(body);
|
|
32301
|
+
} catch {
|
|
32302
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
32303
|
+
}
|
|
32304
|
+
const result = schema.safeParse(json2);
|
|
32305
|
+
if (!result.success) {
|
|
32306
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
32307
|
+
}
|
|
32308
|
+
return result.data;
|
|
32309
|
+
}
|
|
32310
|
+
function withoutTrailingSlashes(endpoint) {
|
|
32311
|
+
let end = endpoint.length;
|
|
32312
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
32313
|
+
return endpoint.slice(0, end);
|
|
32314
|
+
}
|
|
32315
|
+
var SLASH = "/".charCodeAt(0);
|
|
32316
|
+
function createRemoteClient(options) {
|
|
32317
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
32318
|
+
const url2 = (route) => `${base}${route}`;
|
|
32319
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
32320
|
+
return {
|
|
32321
|
+
async ingestEvents(batch) {
|
|
32322
|
+
const response = await send({
|
|
32323
|
+
...common,
|
|
32324
|
+
method: "POST",
|
|
32325
|
+
url: url2(ROUTES.events),
|
|
32326
|
+
body: JSON.stringify(batch)
|
|
32327
|
+
});
|
|
32328
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
32329
|
+
},
|
|
32330
|
+
async ingestInventory(context) {
|
|
32331
|
+
const response = await send({
|
|
32332
|
+
...common,
|
|
32333
|
+
method: "POST",
|
|
32334
|
+
url: url2(ROUTES.inventory),
|
|
32335
|
+
body: JSON.stringify(context)
|
|
32336
|
+
});
|
|
32337
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
32338
|
+
},
|
|
32339
|
+
async recordAuditEvent(event) {
|
|
32340
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
32341
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
32342
|
+
const submission = validated.data;
|
|
32343
|
+
const response = await send({
|
|
32344
|
+
...common,
|
|
32345
|
+
method: "POST",
|
|
32346
|
+
url: url2(ROUTES.auditEvents),
|
|
32347
|
+
body: JSON.stringify(submission)
|
|
32348
|
+
});
|
|
32349
|
+
okBody(response);
|
|
32350
|
+
},
|
|
32351
|
+
async reportStorePosture(snapshot) {
|
|
32352
|
+
const response = await send({
|
|
32353
|
+
...common,
|
|
32354
|
+
method: "POST",
|
|
32355
|
+
url: url2(ROUTES.storePosture),
|
|
32356
|
+
body: JSON.stringify(snapshot)
|
|
32357
|
+
});
|
|
32358
|
+
okBody(response);
|
|
32359
|
+
},
|
|
32360
|
+
async getPolicyBundle(etag) {
|
|
32361
|
+
const response = await send({
|
|
32362
|
+
...common,
|
|
32363
|
+
method: "GET",
|
|
32364
|
+
url: url2(ROUTES.policyBundle),
|
|
32365
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
32366
|
+
});
|
|
32367
|
+
if (response.status === 304) {
|
|
32368
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
32369
|
+
}
|
|
32370
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
32371
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
32372
|
+
},
|
|
32373
|
+
async whoami() {
|
|
32374
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
32375
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
32376
|
+
}
|
|
32377
|
+
};
|
|
32378
|
+
}
|
|
32379
|
+
|
|
32380
|
+
// ../../packages/plugin-runtime/src/attached/posture-reporter.ts
|
|
32381
|
+
var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
32382
|
+
function createPostureReporter(deps) {
|
|
32383
|
+
async function prepare() {
|
|
32384
|
+
try {
|
|
32385
|
+
const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
|
|
32386
|
+
if (state === null) return null;
|
|
32387
|
+
const nowMs = deps.now();
|
|
32388
|
+
const elapsed = nowMs - state.lastAttemptedAtMs;
|
|
32389
|
+
if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
|
|
32390
|
+
try {
|
|
32391
|
+
await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
|
|
32392
|
+
} catch {
|
|
32393
|
+
}
|
|
32394
|
+
const { readError, ...measurement } = deps.readStore();
|
|
32395
|
+
if (readError) return null;
|
|
32396
|
+
let plugin;
|
|
32397
|
+
try {
|
|
32398
|
+
plugin = await deps.pluginBlock?.();
|
|
32399
|
+
} catch {
|
|
32400
|
+
plugin = void 0;
|
|
32401
|
+
}
|
|
32402
|
+
return {
|
|
32403
|
+
deviceId: state.deviceId,
|
|
32404
|
+
hostname: deps.hostname(),
|
|
32405
|
+
capturedAt: nowMs,
|
|
32406
|
+
...measurement,
|
|
32407
|
+
// Omit the key rather than spread an explicit `undefined` —
|
|
32408
|
+
// exactOptionalPropertyTypes distinguishes the two, and the bridge in
|
|
32409
|
+
// factory.ts keys on presence.
|
|
32410
|
+
...plugin === void 0 ? {} : { plugin }
|
|
32411
|
+
};
|
|
32412
|
+
} catch {
|
|
32413
|
+
return null;
|
|
32414
|
+
}
|
|
32415
|
+
}
|
|
32416
|
+
async function send2(snapshot) {
|
|
32417
|
+
try {
|
|
32418
|
+
await deps.report(snapshot);
|
|
32419
|
+
} catch {
|
|
32420
|
+
}
|
|
32421
|
+
}
|
|
32422
|
+
return { prepare, send: send2 };
|
|
32423
|
+
}
|
|
32424
|
+
|
|
32425
|
+
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
32426
|
+
import { statSync as statSync9 } from "fs";
|
|
32427
|
+
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
32428
|
+
|
|
32429
|
+
// ../../packages/plugin-runtime/src/attached/action-counts.ts
|
|
32430
|
+
function emptyActionCounts() {
|
|
32431
|
+
return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
|
|
32432
|
+
}
|
|
32433
|
+
function isActionTaken(value) {
|
|
32434
|
+
return ACTION_TAKEN_KEYS.includes(value);
|
|
32435
|
+
}
|
|
32436
|
+
|
|
32437
|
+
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
32438
|
+
var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
|
|
32439
|
+
function isSchemaAbsent(err) {
|
|
32440
|
+
return err instanceof Error && /no such table/i.test(err.message);
|
|
32441
|
+
}
|
|
32442
|
+
function emptyReadout(readError = false) {
|
|
32443
|
+
const byAction = emptyActionCounts();
|
|
32444
|
+
return {
|
|
32445
|
+
storePresent: false,
|
|
32446
|
+
schemaVersion: null,
|
|
32447
|
+
findingsTotal: 0,
|
|
32448
|
+
findingsFirstAt: null,
|
|
32449
|
+
findingsLastAt: null,
|
|
32450
|
+
packs: [],
|
|
32451
|
+
policyCounts: { total: 0, disabled: 0, byAction },
|
|
32452
|
+
readError
|
|
32453
|
+
};
|
|
32454
|
+
}
|
|
32455
|
+
function readStorePosture(dbPath2) {
|
|
32456
|
+
try {
|
|
32457
|
+
statSync9(dbPath2);
|
|
32458
|
+
} catch (err) {
|
|
32459
|
+
const code = err.code;
|
|
32460
|
+
if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
|
|
32461
|
+
return emptyReadout(true);
|
|
32462
|
+
}
|
|
32463
|
+
let db = null;
|
|
32464
|
+
let version2 = null;
|
|
32465
|
+
let packs2 = [];
|
|
32466
|
+
let policyCounts = {
|
|
32467
|
+
total: 0,
|
|
32468
|
+
disabled: 0,
|
|
32469
|
+
byAction: emptyActionCounts()
|
|
32470
|
+
};
|
|
32471
|
+
let findingsTotal = 0;
|
|
32472
|
+
let findingsFirstAt = null;
|
|
32473
|
+
let findingsLastAt = null;
|
|
32474
|
+
const currentReadout = () => ({
|
|
32475
|
+
storePresent: true,
|
|
32476
|
+
schemaVersion: version2,
|
|
32477
|
+
findingsTotal,
|
|
32478
|
+
findingsFirstAt,
|
|
32479
|
+
findingsLastAt,
|
|
32480
|
+
packs: packs2,
|
|
32481
|
+
policyCounts,
|
|
32482
|
+
readError: false
|
|
32483
|
+
});
|
|
32484
|
+
try {
|
|
32485
|
+
db = new DatabaseSync3(dbPath2, { readOnly: true });
|
|
32486
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
32487
|
+
version2 = db.prepare("PRAGMA user_version").get().user_version;
|
|
32488
|
+
try {
|
|
32489
|
+
const packRows = db.prepare(
|
|
32490
|
+
`SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
|
|
32491
|
+
).all();
|
|
32492
|
+
packs2 = packRows.map((r) => ({
|
|
32493
|
+
packId: `${r.namespace}/${r.pack_id}`,
|
|
32494
|
+
version: r.version,
|
|
32495
|
+
enabled: r.enabled !== 0,
|
|
32496
|
+
updatedAt: r.updated_at == null ? null : String(r.updated_at)
|
|
32497
|
+
}));
|
|
32498
|
+
} catch (err) {
|
|
32499
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32500
|
+
}
|
|
32501
|
+
try {
|
|
32502
|
+
const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
|
|
32503
|
+
const byAction = emptyActionCounts();
|
|
32504
|
+
let disabled = 0;
|
|
32505
|
+
for (const row of policyRows) {
|
|
32506
|
+
if (row.enabled === 0) disabled += 1;
|
|
32507
|
+
if (isActionTaken(row.action)) byAction[row.action] += 1;
|
|
32508
|
+
}
|
|
32509
|
+
policyCounts = { total: policyRows.length, disabled, byAction };
|
|
32510
|
+
} catch (err) {
|
|
32511
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32512
|
+
}
|
|
32513
|
+
try {
|
|
32514
|
+
const agg = db.prepare(
|
|
32515
|
+
`SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
|
|
32516
|
+
FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
|
|
32517
|
+
WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
|
|
32518
|
+
).get();
|
|
32519
|
+
findingsTotal = agg.n;
|
|
32520
|
+
findingsFirstAt = agg.firstAt;
|
|
32521
|
+
findingsLastAt = agg.lastAt;
|
|
32522
|
+
} catch (err) {
|
|
32523
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32524
|
+
}
|
|
32525
|
+
return currentReadout();
|
|
32526
|
+
} catch {
|
|
32527
|
+
return emptyReadout(true);
|
|
32528
|
+
} finally {
|
|
32529
|
+
try {
|
|
32530
|
+
db?.close();
|
|
32531
|
+
} catch {
|
|
32532
|
+
}
|
|
32533
|
+
}
|
|
32534
|
+
}
|
|
32535
|
+
|
|
32536
|
+
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
32537
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
32538
|
+
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
32539
|
+
import { join as join20 } from "path";
|
|
32540
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
32541
|
+
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
32542
|
+
const file2 = join20(dir, "posture-state.json");
|
|
32543
|
+
const legacyFile = legacyDir === void 0 ? null : join20(legacyDir, "posture-state.json");
|
|
32544
|
+
async function persist(state) {
|
|
32545
|
+
await ensureDataDir(dir);
|
|
32546
|
+
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
32547
|
+
try {
|
|
32548
|
+
await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
32549
|
+
await publishByRename(tmp, file2);
|
|
32550
|
+
} catch (err) {
|
|
32551
|
+
await rm2(tmp, { force: true }).catch(() => void 0);
|
|
32552
|
+
throw err;
|
|
32553
|
+
}
|
|
32554
|
+
}
|
|
32555
|
+
async function readFrom(path) {
|
|
32556
|
+
let raw;
|
|
32557
|
+
try {
|
|
32558
|
+
raw = await readFile3(path, "utf8");
|
|
32559
|
+
} catch (err) {
|
|
32560
|
+
const code = err.code;
|
|
32561
|
+
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
32562
|
+
throw err;
|
|
32563
|
+
}
|
|
32564
|
+
try {
|
|
32565
|
+
const parsed2 = JSON.parse(raw);
|
|
32566
|
+
if (typeof parsed2 === "object" && parsed2 !== null) {
|
|
32567
|
+
const record2 = parsed2;
|
|
32568
|
+
if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
|
|
32569
|
+
const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
|
|
32570
|
+
return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
|
|
32571
|
+
}
|
|
32572
|
+
}
|
|
32573
|
+
} catch {
|
|
32574
|
+
}
|
|
32575
|
+
return null;
|
|
32576
|
+
}
|
|
32577
|
+
async function read() {
|
|
32578
|
+
const current = await readFrom(file2);
|
|
32579
|
+
if (current) return current;
|
|
32580
|
+
const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
|
|
32581
|
+
if (legacy) {
|
|
32582
|
+
try {
|
|
32583
|
+
await persist(legacy);
|
|
32584
|
+
} catch {
|
|
32585
|
+
}
|
|
32586
|
+
return legacy;
|
|
32587
|
+
}
|
|
32588
|
+
const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
|
|
32589
|
+
try {
|
|
32590
|
+
await ensureDataDir(dir);
|
|
32591
|
+
if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
|
|
32592
|
+
} catch {
|
|
32593
|
+
return null;
|
|
32594
|
+
}
|
|
32595
|
+
const winner = await readFrom(file2).catch(() => null);
|
|
32596
|
+
if (winner) return winner;
|
|
32597
|
+
try {
|
|
32598
|
+
await persist(fresh);
|
|
32599
|
+
} catch {
|
|
32600
|
+
return null;
|
|
32601
|
+
}
|
|
32602
|
+
return fresh;
|
|
32603
|
+
}
|
|
32604
|
+
async function markAttempted(deviceId, atMs) {
|
|
32605
|
+
await persist({ deviceId, lastAttemptedAtMs: atMs });
|
|
32606
|
+
}
|
|
32607
|
+
return { read, markAttempted, file: file2 };
|
|
32608
|
+
}
|
|
32609
|
+
|
|
32610
|
+
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
32611
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
32612
|
+
import { join as join21 } from "path";
|
|
32613
|
+
|
|
32614
|
+
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
32615
|
+
var REFUSAL_LINES = {
|
|
32616
|
+
unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
|
|
32617
|
+
forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
|
|
32618
|
+
};
|
|
32619
|
+
var OUTCOME_LINES = {
|
|
32620
|
+
ok: "policy synced",
|
|
32621
|
+
"not-modified": "policy up to date",
|
|
32622
|
+
unauthorized: REFUSAL_LINES.unauthorized,
|
|
32623
|
+
forbidden: REFUSAL_LINES.forbidden,
|
|
32624
|
+
unreachable: "control plane unreachable at last attempt",
|
|
32625
|
+
"invalid-bundle": "control plane sent a policy bundle this build cannot read"
|
|
32626
|
+
};
|
|
32627
|
+
|
|
32628
|
+
// ../../packages/plugin-runtime/src/attached/sync-trigger.ts
|
|
32629
|
+
import { spawn } from "child_process";
|
|
32630
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
32631
|
+
var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
|
|
32632
|
+
|
|
32633
|
+
// ../../packages/plugin-runtime/src/attached/factory.ts
|
|
32634
|
+
import { hostname as hostname5 } from "os";
|
|
32635
|
+
|
|
32636
|
+
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
32637
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
32638
|
+
|
|
32639
|
+
// ../../packages/plugin-runtime/src/recorder.ts
|
|
32640
|
+
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
32641
|
+
|
|
32642
|
+
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
32643
|
+
var StandaloneDataGateway = class {
|
|
32644
|
+
db;
|
|
32645
|
+
// Kept for the fingerprint key lookup (exception.key lives beside the store).
|
|
32646
|
+
dataDir;
|
|
32647
|
+
// One notice per gateway — see warnRulesetDiscarded.
|
|
32648
|
+
warnedRulesetDiscarded = false;
|
|
32649
|
+
constructor(dataDir2, detections = [], meta3) {
|
|
32650
|
+
this.db = openLocalDatabase(dataDir2);
|
|
32651
|
+
this.dataDir = dataDir2;
|
|
32652
|
+
this.db.installedPacks.recordInventory(detections, meta3);
|
|
32653
|
+
}
|
|
32654
|
+
recordCapture(record2) {
|
|
32655
|
+
this.db.recordCapture(record2.event, record2.findings);
|
|
32656
|
+
return Promise.resolve();
|
|
32657
|
+
}
|
|
32658
|
+
ensureInventory(ctx) {
|
|
32659
|
+
return Promise.resolve(this.db.ensureInventory(ctx));
|
|
32660
|
+
}
|
|
32661
|
+
recordAuditEvent(event) {
|
|
32662
|
+
this.db.auditEvents.insertAuditEvent(event);
|
|
32663
|
+
return Promise.resolve();
|
|
32664
|
+
}
|
|
32665
|
+
// The id is minted inside the repository from the natural key — the plugin can't
|
|
32666
|
+
// import @akasecurity/persistence to compute it, so the gateway is the boundary that
|
|
32667
|
+
// hands the natural key across. UPSERT-take-MAX → idempotent re-reads that also
|
|
32668
|
+
// converge a streaming partial/final split (see insertLlmCall).
|
|
32669
|
+
recordLlmCall(input) {
|
|
32670
|
+
this.db.auditEvents.insertLlmCall(input);
|
|
32671
|
+
return Promise.resolve();
|
|
32672
|
+
}
|
|
32673
|
+
// One reconcile pass = one transaction. All leaves commit together
|
|
32674
|
+
// (single lock + WAL fsync); a contended SQLITE_BUSY rolls back and rejects so the
|
|
32675
|
+
// reconciler drops the whole pass and recovers it idempotently on the next read.
|
|
32676
|
+
recordLlmCalls(inputs) {
|
|
31213
32677
|
if (inputs.length === 0) return Promise.resolve();
|
|
31214
|
-
return new Promise((
|
|
32678
|
+
return new Promise((resolve2, reject) => {
|
|
31215
32679
|
try {
|
|
31216
32680
|
this.db.auditEvents.runInTransaction(() => {
|
|
31217
32681
|
for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
|
|
31218
32682
|
});
|
|
31219
|
-
|
|
32683
|
+
resolve2();
|
|
31220
32684
|
} catch (err) {
|
|
31221
32685
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
31222
32686
|
}
|
|
@@ -31228,12 +32692,12 @@ var StandaloneDataGateway = class {
|
|
|
31228
32692
|
// drops the whole pass and recovers it idempotently next time.
|
|
31229
32693
|
recordToolCalls(inputs) {
|
|
31230
32694
|
if (inputs.length === 0) return Promise.resolve();
|
|
31231
|
-
return new Promise((
|
|
32695
|
+
return new Promise((resolve2, reject) => {
|
|
31232
32696
|
try {
|
|
31233
32697
|
this.db.auditEvents.runInTransaction(() => {
|
|
31234
32698
|
for (const input of inputs) this.writeToolCall(input);
|
|
31235
32699
|
});
|
|
31236
|
-
|
|
32700
|
+
resolve2();
|
|
31237
32701
|
} catch (err) {
|
|
31238
32702
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
31239
32703
|
}
|
|
@@ -31375,7 +32839,7 @@ var StandaloneDataGateway = class {
|
|
|
31375
32839
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
31376
32840
|
const installed = this.installedScanRules();
|
|
31377
32841
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
31378
|
-
id:
|
|
32842
|
+
id: randomUUID18(),
|
|
31379
32843
|
scope: "global",
|
|
31380
32844
|
target: { ruleId },
|
|
31381
32845
|
action,
|
|
@@ -31529,30 +32993,76 @@ var StandaloneDataGateway = class {
|
|
|
31529
32993
|
}
|
|
31530
32994
|
};
|
|
31531
32995
|
|
|
32996
|
+
// ../../packages/plugin-runtime/src/attached/factory.ts
|
|
32997
|
+
function resolveGatewayForConfig(config2, meta3) {
|
|
32998
|
+
const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
|
|
32999
|
+
try {
|
|
33000
|
+
if (!isAttached(config2.settings)) return local;
|
|
33001
|
+
const connection = config2.settings.controlPlane;
|
|
33002
|
+
if (connection === void 0) return local;
|
|
33003
|
+
const state = readControlPlaneCredentialState(config2.settingsDir, connection);
|
|
33004
|
+
if (!state.usable) return local;
|
|
33005
|
+
const client = createRemoteClient({
|
|
33006
|
+
endpoint: connection.endpoint,
|
|
33007
|
+
apiKey: state.credential.apiKey
|
|
33008
|
+
});
|
|
33009
|
+
const store = createPolicyStore(config2.dataDir);
|
|
33010
|
+
const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
|
|
33011
|
+
const forward = createForwardPolicy({ dir: config2.dataDir });
|
|
33012
|
+
return new AttachedDataGateway({
|
|
33013
|
+
local,
|
|
33014
|
+
client,
|
|
33015
|
+
dataDir: config2.dataDir,
|
|
33016
|
+
readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
|
|
33017
|
+
forward,
|
|
33018
|
+
posture: createPostureReporter({
|
|
33019
|
+
// THROUGH THE BREAKER, and wrapped HERE rather than around
|
|
33020
|
+
// `PostureReporter.send`. The reporter swallows every error by
|
|
33021
|
+
// contract, so a wrap outside it would hand `forward.run` a resolved
|
|
33022
|
+
// promise for a send that failed — recording a SUCCESS, clearing
|
|
33023
|
+
// `consecutiveFailures` and `lastFailure`, and telling `aka status` the
|
|
33024
|
+
// forward recovered when nothing did. Wrapping the raw client call puts
|
|
33025
|
+
// the breaker above the swallow, where it can see the truth.
|
|
33026
|
+
//
|
|
33027
|
+
// What it buys: once the breaker is open — the plane already confirmed
|
|
33028
|
+
// down by the gateway's own writes — this stops paying a request
|
|
33029
|
+
// timeout per throttle interval to re-learn it.
|
|
33030
|
+
report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
|
|
33031
|
+
store: postureStore,
|
|
33032
|
+
readStore: () => readStorePosture(config2.dbPath),
|
|
33033
|
+
hostname: () => hostname5(),
|
|
33034
|
+
now: () => Date.now()
|
|
33035
|
+
})
|
|
33036
|
+
});
|
|
33037
|
+
} catch {
|
|
33038
|
+
return local;
|
|
33039
|
+
}
|
|
33040
|
+
}
|
|
33041
|
+
|
|
31532
33042
|
// ../../packages/plugin-runtime/src/resolve.ts
|
|
31533
|
-
var
|
|
31534
|
-
var defaultGatewayFactory =
|
|
33043
|
+
var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
|
|
33044
|
+
var defaultGatewayFactory = configuredGatewayFactory;
|
|
31535
33045
|
function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
|
|
31536
33046
|
return gatewayFactory(config2, meta3);
|
|
31537
33047
|
}
|
|
31538
33048
|
|
|
31539
33049
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
31540
|
-
import { randomUUID as
|
|
33050
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
31541
33051
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
31542
33052
|
|
|
31543
33053
|
// src/protocol/marker.ts
|
|
31544
33054
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
31545
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
31546
|
-
import { join as
|
|
33055
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync14, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
33056
|
+
import { join as join22 } from "path";
|
|
31547
33057
|
var MARKER_FILE = "protocol-marker";
|
|
31548
33058
|
function mintMarker() {
|
|
31549
33059
|
return randomBytes4(8).toString("hex");
|
|
31550
33060
|
}
|
|
31551
33061
|
function sessionProtocolMarker(dataDir2, sessionId) {
|
|
31552
33062
|
if (!sessionId) return mintMarker();
|
|
31553
|
-
const path =
|
|
33063
|
+
const path = join22(dataDir2, MARKER_FILE);
|
|
31554
33064
|
try {
|
|
31555
|
-
const stored = JSON.parse(
|
|
33065
|
+
const stored = JSON.parse(readFileSync14(path, "utf8"));
|
|
31556
33066
|
if (stored.sessionId === sessionId && typeof stored.marker === "string" && /^[0-9a-f]{16}$/.test(stored.marker)) {
|
|
31557
33067
|
return stored.marker;
|
|
31558
33068
|
}
|
|
@@ -31561,7 +33071,7 @@ function sessionProtocolMarker(dataDir2, sessionId) {
|
|
|
31561
33071
|
const marker = mintMarker();
|
|
31562
33072
|
try {
|
|
31563
33073
|
mkdirSync4(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
|
|
31564
|
-
const tmp =
|
|
33074
|
+
const tmp = join22(dataDir2, `${MARKER_FILE}.tmp`);
|
|
31565
33075
|
writeFileSync7(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
|
|
31566
33076
|
renameSync5(tmp, path);
|
|
31567
33077
|
} catch {
|
|
@@ -31812,7 +33322,7 @@ function responseEmitPayload(toolName, outcome, notes) {
|
|
|
31812
33322
|
|
|
31813
33323
|
// src/hooks/shared.ts
|
|
31814
33324
|
async function readStdin() {
|
|
31815
|
-
return new Promise((
|
|
33325
|
+
return new Promise((resolve2) => {
|
|
31816
33326
|
let data = "";
|
|
31817
33327
|
let settled = false;
|
|
31818
33328
|
const finish = () => {
|
|
@@ -31821,7 +33331,7 @@ async function readStdin() {
|
|
|
31821
33331
|
clearTimeout(timer);
|
|
31822
33332
|
process.stdin.removeListener("data", onData);
|
|
31823
33333
|
process.stdin.removeListener("end", finish);
|
|
31824
|
-
|
|
33334
|
+
resolve2(data);
|
|
31825
33335
|
};
|
|
31826
33336
|
const onData = (chunk) => {
|
|
31827
33337
|
data += chunk;
|
|
@@ -31835,8 +33345,8 @@ async function readStdin() {
|
|
|
31835
33345
|
}
|
|
31836
33346
|
function parseJson(raw) {
|
|
31837
33347
|
try {
|
|
31838
|
-
const
|
|
31839
|
-
return typeof
|
|
33348
|
+
const parsed2 = JSON.parse(raw);
|
|
33349
|
+
return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
|
|
31840
33350
|
} catch {
|
|
31841
33351
|
return null;
|
|
31842
33352
|
}
|
|
@@ -31846,12 +33356,12 @@ function getString(record2, key) {
|
|
|
31846
33356
|
return typeof value === "string" ? value : void 0;
|
|
31847
33357
|
}
|
|
31848
33358
|
function emit(output) {
|
|
31849
|
-
return new Promise((
|
|
33359
|
+
return new Promise((resolve2) => {
|
|
31850
33360
|
let settled = false;
|
|
31851
33361
|
const finish = () => {
|
|
31852
33362
|
if (settled) return;
|
|
31853
33363
|
settled = true;
|
|
31854
|
-
|
|
33364
|
+
resolve2();
|
|
31855
33365
|
};
|
|
31856
33366
|
process.stdout.on("error", finish);
|
|
31857
33367
|
process.stdout.write(JSON.stringify(output), finish);
|
|
@@ -31866,6 +33376,65 @@ function baseMetadata(input) {
|
|
|
31866
33376
|
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
31867
33377
|
}
|
|
31868
33378
|
|
|
33379
|
+
// src/hooks/store-health.ts
|
|
33380
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
33381
|
+
import { dirname as dirname5, join as join23 } from "path";
|
|
33382
|
+
var STORE_REDIRECT_MARKER = "store-redirect-last-session";
|
|
33383
|
+
function markerDirs(dataDir2) {
|
|
33384
|
+
return [dataDir2, dirname5(dataDir2)];
|
|
33385
|
+
}
|
|
33386
|
+
function alreadyClaimed(dirs, marker, sessionId) {
|
|
33387
|
+
return dirs.some((dir) => {
|
|
33388
|
+
try {
|
|
33389
|
+
return readFileSync15(join23(dir, marker), "utf8") === sessionId;
|
|
33390
|
+
} catch {
|
|
33391
|
+
return false;
|
|
33392
|
+
}
|
|
33393
|
+
});
|
|
33394
|
+
}
|
|
33395
|
+
function recordClaim(dirs, marker, sessionId) {
|
|
33396
|
+
for (const dir of dirs) {
|
|
33397
|
+
try {
|
|
33398
|
+
mkdirSync5(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
33399
|
+
writeFileSync8(join23(dir, marker), sessionId, { mode: DATA_FILE_MODE });
|
|
33400
|
+
return;
|
|
33401
|
+
} catch {
|
|
33402
|
+
}
|
|
33403
|
+
}
|
|
33404
|
+
}
|
|
33405
|
+
function storeRedirectedMessage(paths, platform2 = process.platform) {
|
|
33406
|
+
const where = paths.map(({ path, target, holds, missing, mode }) => {
|
|
33407
|
+
if (missing) return `${path} -> ${target} (which does not exist; ${holds} cannot land there)`;
|
|
33408
|
+
const loose = mode !== void 0 && (mode & 63) !== 0 ? ", NOT owner-only" : "";
|
|
33409
|
+
const inherited = mode === void 0 ? "" : ` (${formatMode(mode)}${loose})`;
|
|
33410
|
+
return `${path} -> ${target}${inherited}, holding ${holds}`;
|
|
33411
|
+
}).join("; ");
|
|
33412
|
+
const subject = paths.length === 1 ? "a store path is a symlink" : `${String(paths.length)} store paths are symlinks`;
|
|
33413
|
+
const anyResolves = paths.some(({ missing }) => !missing);
|
|
33414
|
+
const lead = anyResolves ? `${subject}, so AKA is writing into the target instead: ${where}. ` : `${subject} resolving nowhere, so AKA cannot write there: ${where}. `;
|
|
33415
|
+
const kept = anyResolves && platform2 !== "win32" ? "Permissions are never changed through a symlink, so the store keeps whatever the target already had. " : "";
|
|
33416
|
+
return `[aka] ${lead}${kept}If you did not create that link, treat it as untrusted and run \`aka init\` for the full report.
|
|
33417
|
+
`;
|
|
33418
|
+
}
|
|
33419
|
+
function formatMode(mode) {
|
|
33420
|
+
return `0${mode.toString(8).padStart(3, "0")}`;
|
|
33421
|
+
}
|
|
33422
|
+
function warnIfStoreRedirected(config2, sessionId, write = (message) => void process.stderr.write(message)) {
|
|
33423
|
+
try {
|
|
33424
|
+
const paths = symlinkedStorePaths(dirname5(config2.dataDir));
|
|
33425
|
+
if (paths.length === 0) return;
|
|
33426
|
+
if (!sessionId) {
|
|
33427
|
+
write(storeRedirectedMessage(paths));
|
|
33428
|
+
return;
|
|
33429
|
+
}
|
|
33430
|
+
const dirs = markerDirs(config2.dataDir);
|
|
33431
|
+
if (alreadyClaimed(dirs, STORE_REDIRECT_MARKER, sessionId)) return;
|
|
33432
|
+
write(storeRedirectedMessage(paths));
|
|
33433
|
+
recordClaim(dirs, STORE_REDIRECT_MARKER, sessionId);
|
|
33434
|
+
} catch {
|
|
33435
|
+
}
|
|
33436
|
+
}
|
|
33437
|
+
|
|
31869
33438
|
// src/hooks/post-tool-use.ts
|
|
31870
33439
|
async function main() {
|
|
31871
33440
|
const input = parseJson(await readStdin());
|
|
@@ -31885,6 +33454,7 @@ async function main() {
|
|
|
31885
33454
|
const filePath = typeof rawToolInput === "object" && rawToolInput !== null ? getString(rawToolInput, "file_path") : void 0;
|
|
31886
33455
|
if (filePath) metadata.filePath = filePath;
|
|
31887
33456
|
const config2 = loadConfig();
|
|
33457
|
+
warnIfStoreRedirected(config2, getString(input, "session_id"));
|
|
31888
33458
|
const gateway = resolveDataGateway(config2);
|
|
31889
33459
|
const runtime = createPluginRuntime(gateway, config2.settings, { dataDir: config2.dataDir });
|
|
31890
33460
|
const vaultGlue = isVaultConsentValid(config2.settings.vaultConsent) ? createVaultGlue() : null;
|