@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
|
@@ -50,7 +50,7 @@ var require_ignore = __commonJS({
|
|
|
50
50
|
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
51
51
|
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
52
52
|
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
53
|
-
var
|
|
53
|
+
var SLASH2 = "/";
|
|
54
54
|
var TMP_KEY_IGNORE = "node-ignore";
|
|
55
55
|
if (typeof Symbol !== "undefined") {
|
|
56
56
|
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
@@ -422,11 +422,11 @@ var require_ignore = __commonJS({
|
|
|
422
422
|
if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
|
|
423
423
|
return this.test(path);
|
|
424
424
|
}
|
|
425
|
-
const slices = path.split(
|
|
425
|
+
const slices = path.split(SLASH2).filter(Boolean);
|
|
426
426
|
slices.pop();
|
|
427
427
|
if (slices.length) {
|
|
428
428
|
const parent = this._t(
|
|
429
|
-
slices.join(
|
|
429
|
+
slices.join(SLASH2) + SLASH2,
|
|
430
430
|
this._testCache,
|
|
431
431
|
true,
|
|
432
432
|
slices
|
|
@@ -442,14 +442,14 @@ var require_ignore = __commonJS({
|
|
|
442
442
|
return cache[path];
|
|
443
443
|
}
|
|
444
444
|
if (!slices) {
|
|
445
|
-
slices = path.split(
|
|
445
|
+
slices = path.split(SLASH2).filter(Boolean);
|
|
446
446
|
}
|
|
447
447
|
slices.pop();
|
|
448
448
|
if (!slices.length) {
|
|
449
449
|
return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE);
|
|
450
450
|
}
|
|
451
451
|
const parent = this._t(
|
|
452
|
-
slices.join(
|
|
452
|
+
slices.join(SLASH2) + SLASH2,
|
|
453
453
|
cache,
|
|
454
454
|
checkUnignored,
|
|
455
455
|
slices
|
|
@@ -492,13 +492,12 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
|
-
import { existsSync as
|
|
496
|
-
import { join as
|
|
495
|
+
import { existsSync as existsSync6 } from "fs";
|
|
496
|
+
import { join as join10 } from "path";
|
|
497
497
|
|
|
498
|
-
// ../../packages/persistence/src/
|
|
499
|
-
import {
|
|
500
|
-
import { join
|
|
501
|
-
import { DatabaseSync } from "node:sqlite";
|
|
498
|
+
// ../../packages/persistence/src/control-plane-credential.ts
|
|
499
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
|
|
500
|
+
import { join } from "path";
|
|
502
501
|
|
|
503
502
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
504
503
|
var SQLITE_MIGRATIONS = [
|
|
@@ -16202,6 +16201,125 @@ var ConfigScanRecord = external_exports.object({
|
|
|
16202
16201
|
findings: external_exports.array(ConfigPostureFindingInput).optional()
|
|
16203
16202
|
});
|
|
16204
16203
|
|
|
16204
|
+
// ../../packages/schema/src/zod/control-plane.ts
|
|
16205
|
+
var ATTACHED_CREDENTIAL_FILENAME = "control-plane-credential.json";
|
|
16206
|
+
var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
|
|
16207
|
+
var AttachedCredential = external_exports.object({
|
|
16208
|
+
specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
|
|
16209
|
+
// The control-plane endpoint this credential was minted against.
|
|
16210
|
+
endpoint: external_exports.string().min(1),
|
|
16211
|
+
// The bearer credential itself. Never logged, never rendered — status
|
|
16212
|
+
// surfaces show `keyPrefix` and nothing else.
|
|
16213
|
+
apiKey: external_exports.string().min(1),
|
|
16214
|
+
// First few characters of the key, safe to display so a user can match the
|
|
16215
|
+
// credential against their organization's key list.
|
|
16216
|
+
keyPrefix: external_exports.string().min(1).max(16).optional(),
|
|
16217
|
+
mintedAt: external_exports.iso.datetime().optional()
|
|
16218
|
+
});
|
|
16219
|
+
var MAX_DATE_MS = 253402300799999;
|
|
16220
|
+
var MAX_INT4 = 2147483647;
|
|
16221
|
+
var StorePosturePack = external_exports.object({
|
|
16222
|
+
packId: external_exports.string().min(1),
|
|
16223
|
+
// 'namespace/packId'
|
|
16224
|
+
version: external_exports.string().min(1),
|
|
16225
|
+
enabled: external_exports.boolean(),
|
|
16226
|
+
// Stringified pass-through of the local store's `installed_packs.updated_at`
|
|
16227
|
+
// — the column format is store-version-dependent (epoch millis vs ISO), so
|
|
16228
|
+
// the wire shape assumes neither.
|
|
16229
|
+
updatedAt: external_exports.string().nullable()
|
|
16230
|
+
}).meta({ id: "StorePosturePack" });
|
|
16231
|
+
var StorePosturePolicyCounts = external_exports.object({
|
|
16232
|
+
total: external_exports.number().int().min(0),
|
|
16233
|
+
disabled: external_exports.number().int().min(0),
|
|
16234
|
+
// Exhaustive per-action map; the builder pre-fills every action with 0.
|
|
16235
|
+
//
|
|
16236
|
+
// Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
|
|
16237
|
+
// enforces exhaustiveness either way, but z.record emits `propertyNames` +
|
|
16238
|
+
// `additionalProperties` into a generated schema document, and a type
|
|
16239
|
+
// generator renders THAT with every key optional — a sender built against
|
|
16240
|
+
// the generated type would typecheck and still be rejected at runtime. An
|
|
16241
|
+
// explicit object emits `properties` + `required`, so generated types
|
|
16242
|
+
// demand all five.
|
|
16243
|
+
//
|
|
16244
|
+
// `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
|
|
16245
|
+
// ActionTaken member is a COMPILE error here instead of silent drift.
|
|
16246
|
+
// `.strict()` is load-bearing — it rejects an unknown action key, which a
|
|
16247
|
+
// bare object would silently STRIP, accepting a miscounted map as valid.
|
|
16248
|
+
byAction: external_exports.object({
|
|
16249
|
+
warn: external_exports.number().int().min(0),
|
|
16250
|
+
redact: external_exports.number().int().min(0),
|
|
16251
|
+
block: external_exports.number().int().min(0),
|
|
16252
|
+
allow: external_exports.number().int().min(0),
|
|
16253
|
+
log: external_exports.number().int().min(0)
|
|
16254
|
+
}).strict()
|
|
16255
|
+
}).meta({ id: "StorePosturePolicyCounts" });
|
|
16256
|
+
var StorePosturePlugin = external_exports.object({
|
|
16257
|
+
/** Package name of the reporting plugin. */
|
|
16258
|
+
package: external_exports.string().min(1).max(200),
|
|
16259
|
+
version: external_exports.string().min(1).max(64),
|
|
16260
|
+
/** Version of the bundled core, when the build records one separately. */
|
|
16261
|
+
ossVersion: external_exports.string().max(64).nullable(),
|
|
16262
|
+
/**
|
|
16263
|
+
* `version` of the policy bundle this machine last fetched. Bounded at 200
|
|
16264
|
+
* rather than the 64 a bare sha256 hex digest needs today, so a later
|
|
16265
|
+
* format with an algorithm prefix does not start rejecting the channel.
|
|
16266
|
+
*/
|
|
16267
|
+
policyBundleVersion: external_exports.string().max(200).nullable(),
|
|
16268
|
+
/** Epoch millis, on the CLIENT clock, of that fetch. */
|
|
16269
|
+
policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
|
|
16270
|
+
}).meta({ id: "StorePosturePlugin" });
|
|
16271
|
+
var StorePostureSnapshot = external_exports.object({
|
|
16272
|
+
deviceId: external_exports.guid(),
|
|
16273
|
+
hostname: external_exports.string().min(1).max(253),
|
|
16274
|
+
// Epoch millis on the CLIENT clock. Bounded by what a receiving store
|
|
16275
|
+
// accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
|
|
16276
|
+
capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
|
|
16277
|
+
// False is a measurement, not an error state: "no local store exists on
|
|
16278
|
+
// this machine".
|
|
16279
|
+
storePresent: external_exports.boolean(),
|
|
16280
|
+
schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
|
|
16281
|
+
// PRAGMA user_version
|
|
16282
|
+
findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
|
|
16283
|
+
// Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
|
|
16284
|
+
// bound does and does not do. Worth stating for these two specifically:
|
|
16285
|
+
// they are read from the local store's own ROWS rather than from this
|
|
16286
|
+
// machine's clock, so a damaged or hand-edited store is enough to produce
|
|
16287
|
+
// an out-of-range value with no clock skew involved.
|
|
16288
|
+
findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
|
|
16289
|
+
findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
|
|
16290
|
+
packs: external_exports.array(StorePosturePack).max(500),
|
|
16291
|
+
policyCounts: StorePosturePolicyCounts,
|
|
16292
|
+
// OPTIONAL, not nullable: a reporter that predates this member keeps
|
|
16293
|
+
// getting its 200 without a payload change.
|
|
16294
|
+
plugin: StorePosturePlugin.optional()
|
|
16295
|
+
}).meta({ id: "StorePostureSnapshot" });
|
|
16296
|
+
var CAPTURE_VERSION_PREFIX = "capture/";
|
|
16297
|
+
var RecordAuditEventRequest = AuditEventInput.extend({
|
|
16298
|
+
inspections: external_exports.array(ToolCallInspection).default([])
|
|
16299
|
+
}).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
|
|
16300
|
+
message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
|
|
16301
|
+
path: ["inspections"]
|
|
16302
|
+
}).meta({ id: "RecordAuditEventRequest" });
|
|
16303
|
+
var IngestAck = external_exports.object({
|
|
16304
|
+
accepted: external_exports.number().int().nonnegative(),
|
|
16305
|
+
duplicates: external_exports.number().int().nonnegative()
|
|
16306
|
+
});
|
|
16307
|
+
var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
|
|
16308
|
+
var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
|
|
16309
|
+
var PluginWhoami = external_exports.object({
|
|
16310
|
+
tenantName: printable(200),
|
|
16311
|
+
userEmail: printable(320),
|
|
16312
|
+
role: printable(64),
|
|
16313
|
+
keyKind: printable(64),
|
|
16314
|
+
serverTime: printable(64)
|
|
16315
|
+
});
|
|
16316
|
+
var ControlPlaneErrorBody = external_exports.object({
|
|
16317
|
+
error: external_exports.object({
|
|
16318
|
+
code: external_exports.string().optional(),
|
|
16319
|
+
message: external_exports.string().optional()
|
|
16320
|
+
}).optional()
|
|
16321
|
+
});
|
|
16322
|
+
|
|
16205
16323
|
// ../../packages/schema/src/zod/registry.ts
|
|
16206
16324
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
16207
16325
|
var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -16504,15 +16622,15 @@ function summaryToDetectionListItem(s) {
|
|
|
16504
16622
|
}
|
|
16505
16623
|
function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
16506
16624
|
const rules = row.rules.flatMap((r) => {
|
|
16507
|
-
const
|
|
16508
|
-
if (!
|
|
16625
|
+
const parsed2 = Matcher.safeParse(r.matcher);
|
|
16626
|
+
if (!parsed2.success) return [];
|
|
16509
16627
|
return [
|
|
16510
16628
|
{
|
|
16511
16629
|
id: r.id,
|
|
16512
16630
|
name: r.name,
|
|
16513
16631
|
category: r.category,
|
|
16514
16632
|
severity: r.severity,
|
|
16515
|
-
matcher:
|
|
16633
|
+
matcher: parsed2.data
|
|
16516
16634
|
}
|
|
16517
16635
|
];
|
|
16518
16636
|
});
|
|
@@ -17158,8 +17276,8 @@ function toApiAction(dbVal) {
|
|
|
17158
17276
|
}
|
|
17159
17277
|
function toApiCategory(dbVal) {
|
|
17160
17278
|
if (dbVal === "code_context") return "source_code";
|
|
17161
|
-
const
|
|
17162
|
-
return
|
|
17279
|
+
const parsed2 = FindingCategory.safeParse(dbVal);
|
|
17280
|
+
return parsed2.success ? parsed2.data : "custom";
|
|
17163
17281
|
}
|
|
17164
17282
|
function toApiProvider(sourceTool) {
|
|
17165
17283
|
return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
|
|
@@ -17796,6 +17914,9 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17796
17914
|
function defaultWorkspaceSettings() {
|
|
17797
17915
|
return WorkspaceSettings.parse({});
|
|
17798
17916
|
}
|
|
17917
|
+
function isAttached(settings) {
|
|
17918
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
17919
|
+
}
|
|
17799
17920
|
function toInventoryRow(input, id, now) {
|
|
17800
17921
|
return {
|
|
17801
17922
|
id,
|
|
@@ -18063,8 +18184,8 @@ function builtinPolicyIsReversible(id) {
|
|
|
18063
18184
|
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
18064
18185
|
}
|
|
18065
18186
|
function policyIdIsReversible(policyId) {
|
|
18066
|
-
const
|
|
18067
|
-
const id =
|
|
18187
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18188
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
18068
18189
|
return builtinPolicyIsReversible(id);
|
|
18069
18190
|
}
|
|
18070
18191
|
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
@@ -18075,8 +18196,8 @@ var BUILTIN_POLICIES = Object.fromEntries(
|
|
|
18075
18196
|
);
|
|
18076
18197
|
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
18077
18198
|
function policyIdToAction(policyId) {
|
|
18078
|
-
const
|
|
18079
|
-
const id =
|
|
18199
|
+
const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18200
|
+
const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
|
|
18080
18201
|
return BUILTIN_POLICIES[id].action;
|
|
18081
18202
|
}
|
|
18082
18203
|
var UsedByItem = external_exports.object({
|
|
@@ -18519,53 +18640,6 @@ function reviewSeverityRank(reasons) {
|
|
|
18519
18640
|
return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
|
|
18520
18641
|
}
|
|
18521
18642
|
|
|
18522
|
-
// ../../packages/persistence/src/ids.ts
|
|
18523
|
-
import { createHash } from "crypto";
|
|
18524
|
-
function sha256Hex(input) {
|
|
18525
|
-
return createHash("sha256").update(input).digest("hex");
|
|
18526
|
-
}
|
|
18527
|
-
function inventoryId(objectType, identityKey) {
|
|
18528
|
-
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
18529
|
-
}
|
|
18530
|
-
function sourceProjectId(url2) {
|
|
18531
|
-
return sha256Hex(canonicalIdentity(["source_project", url2]));
|
|
18532
|
-
}
|
|
18533
|
-
function classifiedDataId(cls) {
|
|
18534
|
-
return sha256Hex(canonicalIdentity(["classified_data", cls]));
|
|
18535
|
-
}
|
|
18536
|
-
function inspectionDefinitionId(ruleId, version2) {
|
|
18537
|
-
return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
|
|
18538
|
-
}
|
|
18539
|
-
function llmCallId(sessionId, messageId) {
|
|
18540
|
-
return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
|
|
18541
|
-
}
|
|
18542
|
-
function toolCallId(sessionId, toolUseId) {
|
|
18543
|
-
return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
|
|
18544
|
-
}
|
|
18545
|
-
function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
|
|
18546
|
-
return sha256Hex(
|
|
18547
|
-
canonicalIdentity([
|
|
18548
|
-
"inspection_finding",
|
|
18549
|
-
auditEventId,
|
|
18550
|
-
ruleId,
|
|
18551
|
-
String(spanStart),
|
|
18552
|
-
String(spanEnd)
|
|
18553
|
-
])
|
|
18554
|
-
);
|
|
18555
|
-
}
|
|
18556
|
-
var NO_SESSION = "no_session";
|
|
18557
|
-
var NO_PATH = "no_path";
|
|
18558
|
-
function captureId(sessionId, contentHash, filePath = null) {
|
|
18559
|
-
return sha256Hex(
|
|
18560
|
-
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
18561
|
-
);
|
|
18562
|
-
}
|
|
18563
|
-
|
|
18564
|
-
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18565
|
-
import { randomUUID } from "crypto";
|
|
18566
|
-
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18567
|
-
import { basename, dirname, join } from "path";
|
|
18568
|
-
|
|
18569
18643
|
// ../../packages/persistence/src/paths.ts
|
|
18570
18644
|
import {
|
|
18571
18645
|
chmodSync,
|
|
@@ -18691,7 +18765,123 @@ function publishByLink(tmp, file2, data) {
|
|
|
18691
18765
|
}
|
|
18692
18766
|
}
|
|
18693
18767
|
|
|
18768
|
+
// ../../packages/persistence/src/control-plane-credential.ts
|
|
18769
|
+
function controlPlaneCredentialPath(settingsDir2) {
|
|
18770
|
+
return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
|
|
18771
|
+
}
|
|
18772
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
18773
|
+
function isSafeEndpoint(endpoint) {
|
|
18774
|
+
let parsed2;
|
|
18775
|
+
try {
|
|
18776
|
+
parsed2 = new URL(endpoint);
|
|
18777
|
+
} catch {
|
|
18778
|
+
return false;
|
|
18779
|
+
}
|
|
18780
|
+
if (parsed2.protocol === "https:") return true;
|
|
18781
|
+
return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
|
|
18782
|
+
}
|
|
18783
|
+
function repairOrRefuseMode(file2) {
|
|
18784
|
+
const link = lstatSync2(file2, { throwIfNoEntry: false });
|
|
18785
|
+
if (link === void 0) return "absent";
|
|
18786
|
+
if (link.isSymbolicLink()) return "untrusted";
|
|
18787
|
+
const stat = statSync(file2, { throwIfNoEntry: false });
|
|
18788
|
+
if (stat === void 0) return "absent";
|
|
18789
|
+
const uid = process.getuid?.();
|
|
18790
|
+
if (uid !== void 0 && stat.uid !== uid) return "untrusted";
|
|
18791
|
+
if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
|
|
18792
|
+
try {
|
|
18793
|
+
chmodSync2(file2, DATA_FILE_MODE);
|
|
18794
|
+
} catch {
|
|
18795
|
+
return "untrusted";
|
|
18796
|
+
}
|
|
18797
|
+
}
|
|
18798
|
+
return "ok";
|
|
18799
|
+
}
|
|
18800
|
+
function readControlPlaneCredentialState(settingsDir2, connection) {
|
|
18801
|
+
const file2 = controlPlaneCredentialPath(settingsDir2);
|
|
18802
|
+
let raw;
|
|
18803
|
+
const gate = repairOrRefuseMode(file2);
|
|
18804
|
+
if (gate === "absent") return { usable: false, reason: "absent" };
|
|
18805
|
+
if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
|
|
18806
|
+
try {
|
|
18807
|
+
raw = readFileSync(file2, "utf8");
|
|
18808
|
+
} catch (err) {
|
|
18809
|
+
const code = err.code;
|
|
18810
|
+
return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
|
|
18811
|
+
}
|
|
18812
|
+
let parsed2;
|
|
18813
|
+
try {
|
|
18814
|
+
parsed2 = JSON.parse(raw);
|
|
18815
|
+
} catch {
|
|
18816
|
+
return { usable: false, reason: "malformed" };
|
|
18817
|
+
}
|
|
18818
|
+
const result = AttachedCredential.safeParse(parsed2);
|
|
18819
|
+
if (!result.success) return { usable: false, reason: "malformed" };
|
|
18820
|
+
if (!isSafeEndpoint(result.data.endpoint)) {
|
|
18821
|
+
return { usable: false, reason: "unsafe-endpoint" };
|
|
18822
|
+
}
|
|
18823
|
+
if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
|
|
18824
|
+
return {
|
|
18825
|
+
usable: false,
|
|
18826
|
+
reason: "endpoint-mismatch",
|
|
18827
|
+
credentialEndpoint: result.data.endpoint,
|
|
18828
|
+
settingsEndpoint: connection.endpoint
|
|
18829
|
+
};
|
|
18830
|
+
}
|
|
18831
|
+
return { usable: true, credential: result.data };
|
|
18832
|
+
}
|
|
18833
|
+
|
|
18834
|
+
// ../../packages/persistence/src/database.ts
|
|
18835
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
18836
|
+
import { join as join3, sep } from "path";
|
|
18837
|
+
import { DatabaseSync } from "node:sqlite";
|
|
18838
|
+
|
|
18839
|
+
// ../../packages/persistence/src/ids.ts
|
|
18840
|
+
import { createHash } from "crypto";
|
|
18841
|
+
function sha256Hex(input) {
|
|
18842
|
+
return createHash("sha256").update(input).digest("hex");
|
|
18843
|
+
}
|
|
18844
|
+
function inventoryId(objectType, identityKey) {
|
|
18845
|
+
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
18846
|
+
}
|
|
18847
|
+
function sourceProjectId(url2) {
|
|
18848
|
+
return sha256Hex(canonicalIdentity(["source_project", url2]));
|
|
18849
|
+
}
|
|
18850
|
+
function classifiedDataId(cls) {
|
|
18851
|
+
return sha256Hex(canonicalIdentity(["classified_data", cls]));
|
|
18852
|
+
}
|
|
18853
|
+
function inspectionDefinitionId(ruleId, version2) {
|
|
18854
|
+
return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
|
|
18855
|
+
}
|
|
18856
|
+
function llmCallId(sessionId, messageId) {
|
|
18857
|
+
return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
|
|
18858
|
+
}
|
|
18859
|
+
function toolCallId(sessionId, toolUseId) {
|
|
18860
|
+
return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
|
|
18861
|
+
}
|
|
18862
|
+
function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
|
|
18863
|
+
return sha256Hex(
|
|
18864
|
+
canonicalIdentity([
|
|
18865
|
+
"inspection_finding",
|
|
18866
|
+
auditEventId,
|
|
18867
|
+
ruleId,
|
|
18868
|
+
String(spanStart),
|
|
18869
|
+
String(spanEnd)
|
|
18870
|
+
])
|
|
18871
|
+
);
|
|
18872
|
+
}
|
|
18873
|
+
var NO_SESSION = "no_session";
|
|
18874
|
+
var NO_PATH = "no_path";
|
|
18875
|
+
function captureId(sessionId, contentHash, filePath = null) {
|
|
18876
|
+
return sha256Hex(
|
|
18877
|
+
canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
|
|
18878
|
+
);
|
|
18879
|
+
}
|
|
18880
|
+
|
|
18694
18881
|
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18882
|
+
import { randomUUID } from "crypto";
|
|
18883
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
|
|
18884
|
+
import { basename, dirname, join as join2 } from "path";
|
|
18695
18885
|
function backupPath(file2, tag) {
|
|
18696
18886
|
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18697
18887
|
}
|
|
@@ -18701,15 +18891,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
|
|
|
18701
18891
|
var SNAPSHOT_STAGING_COPY = "copy";
|
|
18702
18892
|
function createSnapshotStaging(backup) {
|
|
18703
18893
|
const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
|
|
18704
|
-
|
|
18894
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18705
18895
|
mkdirOwnerOnlySync(stage);
|
|
18706
18896
|
tightenDir(stage);
|
|
18707
|
-
return { stage, copy:
|
|
18897
|
+
return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
|
|
18708
18898
|
}
|
|
18709
18899
|
function idleMs(entry) {
|
|
18710
|
-
for (const candidate of [
|
|
18900
|
+
for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
|
|
18711
18901
|
try {
|
|
18712
|
-
return Date.now() -
|
|
18902
|
+
return Date.now() - statSync2(candidate).mtimeMs;
|
|
18713
18903
|
} catch {
|
|
18714
18904
|
}
|
|
18715
18905
|
}
|
|
@@ -18726,11 +18916,11 @@ function reapStalePartials(file2) {
|
|
|
18726
18916
|
}
|
|
18727
18917
|
for (const name of entries) {
|
|
18728
18918
|
if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
|
|
18729
|
-
const staging =
|
|
18919
|
+
const staging = join2(dir, name);
|
|
18730
18920
|
try {
|
|
18731
18921
|
const idle = idleMs(staging);
|
|
18732
18922
|
if (idle !== null && idle > STALE_PARTIAL_MS) {
|
|
18733
|
-
|
|
18923
|
+
rmSync3(staging, { recursive: true, force: true });
|
|
18734
18924
|
}
|
|
18735
18925
|
} catch {
|
|
18736
18926
|
}
|
|
@@ -18744,13 +18934,13 @@ function snapshotStore(db, backup) {
|
|
|
18744
18934
|
renameSync2(copy, backup);
|
|
18745
18935
|
} catch (error51) {
|
|
18746
18936
|
try {
|
|
18747
|
-
|
|
18937
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18748
18938
|
} catch {
|
|
18749
18939
|
}
|
|
18750
18940
|
throw error51;
|
|
18751
18941
|
}
|
|
18752
18942
|
try {
|
|
18753
|
-
|
|
18943
|
+
rmSync3(stage, { recursive: true, force: true });
|
|
18754
18944
|
} catch {
|
|
18755
18945
|
}
|
|
18756
18946
|
}
|
|
@@ -18765,7 +18955,7 @@ function moveStoreAside(file2, backup) {
|
|
|
18765
18955
|
renameSync2(sidecar, moved);
|
|
18766
18956
|
undo.push([moved, sidecar]);
|
|
18767
18957
|
} catch {
|
|
18768
|
-
|
|
18958
|
+
rmSync3(sidecar, { force: true });
|
|
18769
18959
|
}
|
|
18770
18960
|
}
|
|
18771
18961
|
} catch (error51) {
|
|
@@ -18781,14 +18971,14 @@ function moveStoreAside(file2, backup) {
|
|
|
18781
18971
|
}
|
|
18782
18972
|
function discardStore(file2, backup) {
|
|
18783
18973
|
try {
|
|
18784
|
-
|
|
18974
|
+
rmSync3(file2, { force: true });
|
|
18785
18975
|
for (const sidecar of dbSidecars(file2)) {
|
|
18786
|
-
|
|
18976
|
+
rmSync3(sidecar, { force: true });
|
|
18787
18977
|
}
|
|
18788
18978
|
} catch (error51) {
|
|
18789
18979
|
if (existsSync(file2)) {
|
|
18790
18980
|
try {
|
|
18791
|
-
|
|
18981
|
+
rmSync3(backup, { force: true });
|
|
18792
18982
|
} catch {
|
|
18793
18983
|
}
|
|
18794
18984
|
}
|
|
@@ -19020,10 +19210,31 @@ function applyMigrations(db, file2) {
|
|
|
19020
19210
|
if (drained) applyLegacyDropMigration(db, file2);
|
|
19021
19211
|
}
|
|
19022
19212
|
}
|
|
19213
|
+
function readLegacyTables(db) {
|
|
19214
|
+
let holdsRows = false;
|
|
19215
|
+
const marks = [];
|
|
19216
|
+
for (const table of ["events", "findings"]) {
|
|
19217
|
+
try {
|
|
19218
|
+
const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table}`).get();
|
|
19219
|
+
if (row === void 0) {
|
|
19220
|
+
holdsRows = true;
|
|
19221
|
+
marks.push(`${table}:unreadable`);
|
|
19222
|
+
continue;
|
|
19223
|
+
}
|
|
19224
|
+
if (row.n > 0) holdsRows = true;
|
|
19225
|
+
marks.push(`${table}:${String(row.n)}:${String(row.hi)}`);
|
|
19226
|
+
} catch {
|
|
19227
|
+
holdsRows = true;
|
|
19228
|
+
marks.push(`${table}:unreadable`);
|
|
19229
|
+
}
|
|
19230
|
+
}
|
|
19231
|
+
return { holdsRows, mark: marks.join("|") };
|
|
19232
|
+
}
|
|
19023
19233
|
function applyLegacyDropMigration(db, file2) {
|
|
19024
19234
|
const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
|
|
19025
19235
|
if (!migration) return;
|
|
19026
|
-
|
|
19236
|
+
const before = file2 === void 0 ? void 0 : readLegacyTables(db);
|
|
19237
|
+
if (file2 !== void 0 && before?.holdsRows === true) {
|
|
19027
19238
|
try {
|
|
19028
19239
|
backupBeforeLegacyDrop(db, file2);
|
|
19029
19240
|
} catch (error51) {
|
|
@@ -19037,6 +19248,12 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
19037
19248
|
() => {
|
|
19038
19249
|
const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
|
|
19039
19250
|
if (alreadyDropped) return;
|
|
19251
|
+
if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
|
|
19252
|
+
akaWarn(
|
|
19253
|
+
"legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
|
|
19254
|
+
);
|
|
19255
|
+
return;
|
|
19256
|
+
}
|
|
19040
19257
|
for (const statement of splitStatements(migration.sql)) {
|
|
19041
19258
|
db.exec(statement);
|
|
19042
19259
|
}
|
|
@@ -19391,8 +19608,8 @@ function safeJson(s, fallback) {
|
|
|
19391
19608
|
function parseJsonObject(s) {
|
|
19392
19609
|
if (s == null) return void 0;
|
|
19393
19610
|
try {
|
|
19394
|
-
const
|
|
19395
|
-
if (typeof
|
|
19611
|
+
const parsed2 = JSON.parse(s);
|
|
19612
|
+
if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
|
|
19396
19613
|
} catch {
|
|
19397
19614
|
}
|
|
19398
19615
|
return void 0;
|
|
@@ -19403,16 +19620,16 @@ function encodeKeysetCursor(payload) {
|
|
|
19403
19620
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19404
19621
|
}
|
|
19405
19622
|
function decodeKeysetCursor(cursor) {
|
|
19406
|
-
const
|
|
19407
|
-
if (
|
|
19623
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19624
|
+
if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19408
19625
|
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19409
19626
|
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19410
19627
|
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19411
19628
|
// outcome a cursor that does not decode must never produce, since the
|
|
19412
19629
|
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19413
19630
|
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19414
|
-
Number.isInteger(
|
|
19415
|
-
return
|
|
19631
|
+
Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
|
|
19632
|
+
return parsed2;
|
|
19416
19633
|
}
|
|
19417
19634
|
return null;
|
|
19418
19635
|
}
|
|
@@ -19477,18 +19694,18 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
19477
19694
|
};
|
|
19478
19695
|
function safeParseStringArray(raw) {
|
|
19479
19696
|
if (!raw) return [];
|
|
19480
|
-
const
|
|
19481
|
-
return Array.isArray(
|
|
19697
|
+
const parsed2 = safeJson(raw, null);
|
|
19698
|
+
return Array.isArray(parsed2) ? parsed2 : [];
|
|
19482
19699
|
}
|
|
19483
19700
|
var DEFAULT_HARNESS = HARNESS.ClaudeCode;
|
|
19484
19701
|
function toHarness(raw) {
|
|
19485
|
-
const
|
|
19486
|
-
return
|
|
19702
|
+
const parsed2 = Harness.safeParse(raw);
|
|
19703
|
+
return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
|
|
19487
19704
|
}
|
|
19488
19705
|
function resolveLifecycle(row, lastActivityMs, nowMs) {
|
|
19489
19706
|
if (row.status) {
|
|
19490
|
-
const
|
|
19491
|
-
if (
|
|
19707
|
+
const parsed2 = SessionStatus.safeParse(row.status);
|
|
19708
|
+
if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
|
|
19492
19709
|
}
|
|
19493
19710
|
if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
|
|
19494
19711
|
if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
|
|
@@ -20447,9 +20664,9 @@ var SqliteDetectionsRepository = class {
|
|
|
20447
20664
|
const ruleIds = /* @__PURE__ */ new Set();
|
|
20448
20665
|
for (const r of rows) {
|
|
20449
20666
|
if (intToBool(r.enabled)) active += 1;
|
|
20450
|
-
const
|
|
20451
|
-
rules +=
|
|
20452
|
-
for (const rule of
|
|
20667
|
+
const parsed2 = parseRules(r.rulesJson);
|
|
20668
|
+
rules += parsed2.length;
|
|
20669
|
+
for (const rule of parsed2) {
|
|
20453
20670
|
if (typeof rule.id === "string") ruleIds.add(rule.id);
|
|
20454
20671
|
}
|
|
20455
20672
|
}
|
|
@@ -20983,12 +21200,12 @@ function encodeGroupCursor(group) {
|
|
|
20983
21200
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20984
21201
|
}
|
|
20985
21202
|
function decodeGroupCursor(cursor) {
|
|
20986
|
-
const
|
|
20987
|
-
if (
|
|
21203
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
21204
|
+
if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
|
|
20988
21205
|
return {
|
|
20989
|
-
severity:
|
|
20990
|
-
latestDetectedAt:
|
|
20991
|
-
id:
|
|
21206
|
+
severity: parsed2.sev,
|
|
21207
|
+
latestDetectedAt: parsed2.t,
|
|
21208
|
+
id: parsed2.id
|
|
20992
21209
|
};
|
|
20993
21210
|
}
|
|
20994
21211
|
return null;
|
|
@@ -22122,16 +22339,16 @@ var SqliteInstalledPacksRepository = class {
|
|
|
22122
22339
|
continue;
|
|
22123
22340
|
}
|
|
22124
22341
|
for (const entry of raw) {
|
|
22125
|
-
const
|
|
22126
|
-
if (
|
|
22127
|
-
out.rules.push(
|
|
22128
|
-
out.ruleActions.set(
|
|
22129
|
-
out.ruleVersions.set(
|
|
22130
|
-
if (reversible) out.reversibleRules.add(
|
|
22131
|
-
else out.reversibleRules.delete(
|
|
22342
|
+
const parsed2 = Rule.safeParse(entry);
|
|
22343
|
+
if (parsed2.success) {
|
|
22344
|
+
out.rules.push(parsed2.data);
|
|
22345
|
+
out.ruleActions.set(parsed2.data.id, action);
|
|
22346
|
+
out.ruleVersions.set(parsed2.data.id, row.version);
|
|
22347
|
+
if (reversible) out.reversibleRules.add(parsed2.data.id);
|
|
22348
|
+
else out.reversibleRules.delete(parsed2.data.id);
|
|
22132
22349
|
} else {
|
|
22133
22350
|
out.invalidRules += 1;
|
|
22134
|
-
reject(pack, printableRuleId(entry), firstIssueReason(
|
|
22351
|
+
reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
|
|
22135
22352
|
}
|
|
22136
22353
|
}
|
|
22137
22354
|
}
|
|
@@ -23521,15 +23738,15 @@ function encodeReuseCursor(payload) {
|
|
|
23521
23738
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23522
23739
|
}
|
|
23523
23740
|
function decodeReuseCursor(cursor) {
|
|
23524
|
-
const
|
|
23525
|
-
if (
|
|
23741
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23742
|
+
if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23526
23743
|
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23527
23744
|
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23528
23745
|
// malformed cursor must never produce, since restarting from the top is the
|
|
23529
23746
|
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23530
23747
|
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23531
|
-
Number.isInteger(
|
|
23532
|
-
return { occurrences:
|
|
23748
|
+
Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
|
|
23749
|
+
return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
|
|
23533
23750
|
}
|
|
23534
23751
|
return null;
|
|
23535
23752
|
}
|
|
@@ -25258,7 +25475,7 @@ function openAndInitialize(file2) {
|
|
|
25258
25475
|
}
|
|
25259
25476
|
function openLocalDatabase(dir) {
|
|
25260
25477
|
ensureDataDirSync(dir);
|
|
25261
|
-
const file2 =
|
|
25478
|
+
const file2 = join3(dir, DB_FILENAME);
|
|
25262
25479
|
reapStalePartials(file2);
|
|
25263
25480
|
const {
|
|
25264
25481
|
db,
|
|
@@ -25514,9 +25731,9 @@ import {
|
|
|
25514
25731
|
closeSync,
|
|
25515
25732
|
existsSync as existsSync2,
|
|
25516
25733
|
openSync,
|
|
25517
|
-
readFileSync,
|
|
25518
|
-
rmSync as
|
|
25519
|
-
statSync as
|
|
25734
|
+
readFileSync as readFileSync2,
|
|
25735
|
+
rmSync as rmSync4,
|
|
25736
|
+
statSync as statSync3,
|
|
25520
25737
|
writeFileSync as writeFileSync2
|
|
25521
25738
|
} from "fs";
|
|
25522
25739
|
import { hostname as hostname3 } from "os";
|
|
@@ -25534,20 +25751,20 @@ function computeFindingKey(input) {
|
|
|
25534
25751
|
|
|
25535
25752
|
// ../../packages/persistence/src/fingerprint.ts
|
|
25536
25753
|
import { createHmac, randomBytes } from "crypto";
|
|
25537
|
-
import { existsSync as existsSync3, readFileSync as
|
|
25538
|
-
import { join as
|
|
25754
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
25755
|
+
import { join as join4 } from "path";
|
|
25539
25756
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
25540
25757
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
25541
25758
|
var KEY_MATERIAL_BYTES = 32;
|
|
25542
25759
|
function keyFilePath(dataDir2) {
|
|
25543
|
-
return
|
|
25760
|
+
return join4(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
25544
25761
|
}
|
|
25545
25762
|
function parseKeyFile(raw) {
|
|
25546
|
-
const
|
|
25547
|
-
if (typeof
|
|
25763
|
+
const parsed2 = JSON.parse(raw);
|
|
25764
|
+
if (typeof parsed2 !== "object" || parsed2 === null) {
|
|
25548
25765
|
throw new Error("exception key file is corrupt: not a JSON object");
|
|
25549
25766
|
}
|
|
25550
|
-
const { version: version2, material } =
|
|
25767
|
+
const { version: version2, material } = parsed2;
|
|
25551
25768
|
if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
|
|
25552
25769
|
throw new Error("exception key file is corrupt: bad version");
|
|
25553
25770
|
}
|
|
@@ -25578,7 +25795,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
25578
25795
|
}
|
|
25579
25796
|
};
|
|
25580
25797
|
function storedKeyVersionFloor(dataDir2) {
|
|
25581
|
-
const file2 =
|
|
25798
|
+
const file2 = join4(dataDir2, DB_FILENAME);
|
|
25582
25799
|
if (!existsSync3(file2)) return 0;
|
|
25583
25800
|
let db;
|
|
25584
25801
|
try {
|
|
@@ -25633,7 +25850,7 @@ function occupantMessage(file2, kind) {
|
|
|
25633
25850
|
function readFingerprintKey(dataDir2) {
|
|
25634
25851
|
let raw;
|
|
25635
25852
|
try {
|
|
25636
|
-
raw =
|
|
25853
|
+
raw = readFileSync3(keyFilePath(dataDir2), "utf8");
|
|
25637
25854
|
} catch (err) {
|
|
25638
25855
|
if (err.code === "ENOENT") return null;
|
|
25639
25856
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -25659,21 +25876,25 @@ function fingerprintValue(key, raw) {
|
|
|
25659
25876
|
import { renameSync as renameSync3 } from "fs";
|
|
25660
25877
|
import { mkdir } from "fs/promises";
|
|
25661
25878
|
import { homedir } from "os";
|
|
25662
|
-
import { join as
|
|
25879
|
+
import { join as join5 } from "path";
|
|
25663
25880
|
function defaultDataDir() {
|
|
25664
|
-
return
|
|
25881
|
+
return join5(homedir(), ".aka");
|
|
25665
25882
|
}
|
|
25666
25883
|
function settingsDir(base = defaultDataDir()) {
|
|
25667
|
-
return
|
|
25884
|
+
return join5(base, "settings");
|
|
25668
25885
|
}
|
|
25669
25886
|
function dataDir(base = defaultDataDir()) {
|
|
25670
|
-
return
|
|
25887
|
+
return join5(base, "data");
|
|
25671
25888
|
}
|
|
25672
25889
|
function dbPath(base = defaultDataDir()) {
|
|
25673
|
-
return
|
|
25890
|
+
return join5(dataDir(base), "aka.db");
|
|
25674
25891
|
}
|
|
25675
25892
|
function keysDir(base = defaultDataDir()) {
|
|
25676
|
-
return
|
|
25893
|
+
return join5(base, "keys");
|
|
25894
|
+
}
|
|
25895
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
25896
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
25897
|
+
tightenDir(dir);
|
|
25677
25898
|
}
|
|
25678
25899
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
25679
25900
|
ensureDataDirSync(dir);
|
|
@@ -25686,8 +25907,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25686
25907
|
for (const { name, dest } of moves) {
|
|
25687
25908
|
try {
|
|
25688
25909
|
ensureDataDirSync(dest);
|
|
25689
|
-
const moved =
|
|
25690
|
-
renameSync3(
|
|
25910
|
+
const moved = join5(dest, name);
|
|
25911
|
+
renameSync3(join5(base, name), moved);
|
|
25691
25912
|
tightenFile(moved);
|
|
25692
25913
|
} catch {
|
|
25693
25914
|
}
|
|
@@ -25695,7 +25916,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25695
25916
|
}
|
|
25696
25917
|
|
|
25697
25918
|
// ../../packages/persistence/src/managed-settings.ts
|
|
25698
|
-
import { readFileSync as
|
|
25919
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
25699
25920
|
import { posix, win32 } from "path";
|
|
25700
25921
|
function managedSettingsPaths(platform2 = process.platform) {
|
|
25701
25922
|
if (platform2 === "darwin") {
|
|
@@ -25713,14 +25934,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
|
|
|
25713
25934
|
for (const path of paths) {
|
|
25714
25935
|
let text;
|
|
25715
25936
|
try {
|
|
25716
|
-
text =
|
|
25937
|
+
text = readFileSync4(path, "utf8");
|
|
25717
25938
|
} catch {
|
|
25718
25939
|
continue;
|
|
25719
25940
|
}
|
|
25720
25941
|
const record2 = parseJsonObject(text);
|
|
25721
25942
|
if (!record2) continue;
|
|
25722
|
-
const
|
|
25723
|
-
if (
|
|
25943
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
25944
|
+
if (parsed2.success) return parsed2.data;
|
|
25724
25945
|
}
|
|
25725
25946
|
return null;
|
|
25726
25947
|
}
|
|
@@ -25760,14 +25981,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
|
|
|
25760
25981
|
}
|
|
25761
25982
|
|
|
25762
25983
|
// ../../packages/persistence/src/settings.ts
|
|
25763
|
-
import { readFileSync as
|
|
25764
|
-
import { join as
|
|
25984
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
25985
|
+
import { join as join6 } from "path";
|
|
25765
25986
|
var SETTINGS_FILENAME = "settings.json";
|
|
25766
25987
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
25767
25988
|
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
25768
25989
|
}
|
|
25769
25990
|
function readUserSettings(base) {
|
|
25770
|
-
const record2 = readJson(
|
|
25991
|
+
const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
|
|
25771
25992
|
if (!record2) return defaultWorkspaceSettings();
|
|
25772
25993
|
try {
|
|
25773
25994
|
return WorkspaceSettings.parse(record2);
|
|
@@ -25778,13 +25999,64 @@ function readUserSettings(base) {
|
|
|
25778
25999
|
function readJson(file2) {
|
|
25779
26000
|
let text;
|
|
25780
26001
|
try {
|
|
25781
|
-
text =
|
|
26002
|
+
text = readFileSync5(file2, "utf8");
|
|
25782
26003
|
} catch {
|
|
25783
26004
|
return null;
|
|
25784
26005
|
}
|
|
25785
26006
|
return parseJsonObject(text) ?? null;
|
|
25786
26007
|
}
|
|
25787
26008
|
|
|
26009
|
+
// ../../packages/persistence/src/store-symlinks.ts
|
|
26010
|
+
import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
26011
|
+
import { dirname as dirname2, join as join7, resolve } from "path";
|
|
26012
|
+
var STORE_DB = "the store database (including the prompt corpus)";
|
|
26013
|
+
var STORE_SETTINGS = "your settings file";
|
|
26014
|
+
function storeContents(home) {
|
|
26015
|
+
return /* @__PURE__ */ new Map([
|
|
26016
|
+
[home, "the store (including the prompt corpus in aka.db)"],
|
|
26017
|
+
[settingsDir(home), STORE_SETTINGS],
|
|
26018
|
+
[dataDir(home), STORE_DB],
|
|
26019
|
+
[keysDir(home), "the vault key"],
|
|
26020
|
+
[join7(settingsDir(home), "settings.json"), STORE_SETTINGS],
|
|
26021
|
+
[dbPath(home), STORE_DB]
|
|
26022
|
+
]);
|
|
26023
|
+
}
|
|
26024
|
+
function symlinkedStorePaths(home, platform2 = process.platform) {
|
|
26025
|
+
return [...storeContents(home)].flatMap(([path, holds]) => {
|
|
26026
|
+
try {
|
|
26027
|
+
if (!lstatSync3(path).isSymbolicLink()) return [];
|
|
26028
|
+
return [
|
|
26029
|
+
{
|
|
26030
|
+
path,
|
|
26031
|
+
target: linkTarget(path),
|
|
26032
|
+
holds,
|
|
26033
|
+
// existsSync follows the link, so a target that is gone reads as
|
|
26034
|
+
// absent here while lstat above still sees the link itself.
|
|
26035
|
+
missing: !existsSync4(path),
|
|
26036
|
+
mode: targetMode(path, platform2)
|
|
26037
|
+
}
|
|
26038
|
+
];
|
|
26039
|
+
} catch {
|
|
26040
|
+
return [];
|
|
26041
|
+
}
|
|
26042
|
+
});
|
|
26043
|
+
}
|
|
26044
|
+
function linkTarget(path) {
|
|
26045
|
+
try {
|
|
26046
|
+
return realpathSync(path);
|
|
26047
|
+
} catch {
|
|
26048
|
+
return resolve(dirname2(path), readlinkSync(path));
|
|
26049
|
+
}
|
|
26050
|
+
}
|
|
26051
|
+
function targetMode(path, platform2) {
|
|
26052
|
+
if (platform2 === "win32") return void 0;
|
|
26053
|
+
try {
|
|
26054
|
+
return statSync4(path).mode & 511;
|
|
26055
|
+
} catch {
|
|
26056
|
+
return void 0;
|
|
26057
|
+
}
|
|
26058
|
+
}
|
|
26059
|
+
|
|
25788
26060
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
25789
26061
|
import {
|
|
25790
26062
|
createCipheriv,
|
|
@@ -25896,8 +26168,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
25896
26168
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25897
26169
|
import { execFileSync } from "child_process";
|
|
25898
26170
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
25899
|
-
import { chmodSync as
|
|
25900
|
-
import { join as
|
|
26171
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
26172
|
+
import { join as join8 } from "path";
|
|
25901
26173
|
var VAULT_OCCUPANT_REASON = {
|
|
25902
26174
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25903
26175
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -25916,11 +26188,11 @@ var KEY_MATERIAL_BYTES2 = 32;
|
|
|
25916
26188
|
var KEYCHAIN_SERVICE = "aka-vault";
|
|
25917
26189
|
var KEYCHAIN_ACCOUNT = "keyring";
|
|
25918
26190
|
function parseKeyring(raw) {
|
|
25919
|
-
const
|
|
25920
|
-
if (typeof
|
|
26191
|
+
const parsed2 = JSON.parse(raw);
|
|
26192
|
+
if (typeof parsed2 !== "object" || parsed2 === null) {
|
|
25921
26193
|
throw new Error("vault key file is corrupt: not a JSON object");
|
|
25922
26194
|
}
|
|
25923
|
-
const { current, keys } =
|
|
26195
|
+
const { current, keys } = parsed2;
|
|
25924
26196
|
if (typeof current !== "number" || !Number.isInteger(current) || current < 1) {
|
|
25925
26197
|
throw new Error("vault key file is corrupt: bad current version");
|
|
25926
26198
|
}
|
|
@@ -25996,28 +26268,28 @@ function claimRotationLock(lock, owner) {
|
|
|
25996
26268
|
throw asError(err);
|
|
25997
26269
|
}
|
|
25998
26270
|
try {
|
|
25999
|
-
writeFileSync3(
|
|
26271
|
+
writeFileSync3(join8(lock, LOCK_OWNER_FILE), `${owner}
|
|
26000
26272
|
`, { mode: DATA_FILE_MODE });
|
|
26001
26273
|
return true;
|
|
26002
26274
|
} catch (err) {
|
|
26003
|
-
|
|
26275
|
+
rmSync5(lock, { recursive: true, force: true });
|
|
26004
26276
|
throw asError(err);
|
|
26005
26277
|
}
|
|
26006
26278
|
}
|
|
26007
26279
|
function acquireRotationLock(keysDir2) {
|
|
26008
|
-
const lock =
|
|
26280
|
+
const lock = join8(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
26009
26281
|
const owner = randomBytes2(16).toString("hex");
|
|
26010
26282
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
26011
26283
|
let held;
|
|
26012
26284
|
try {
|
|
26013
|
-
held =
|
|
26285
|
+
held = statSync5(lock);
|
|
26014
26286
|
} catch {
|
|
26015
26287
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26016
26288
|
}
|
|
26017
26289
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
26018
26290
|
const aside = `${lock}.stale.${owner}`;
|
|
26019
26291
|
try {
|
|
26020
|
-
const now =
|
|
26292
|
+
const now = statSync5(lock);
|
|
26021
26293
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
26022
26294
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26023
26295
|
}
|
|
@@ -26026,17 +26298,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
26026
26298
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
26027
26299
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
26028
26300
|
}
|
|
26029
|
-
|
|
26301
|
+
rmSync5(aside, { recursive: true, force: true });
|
|
26030
26302
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
26031
26303
|
return { lock, owner };
|
|
26032
26304
|
}
|
|
26033
26305
|
function releaseRotationLock(lease) {
|
|
26034
26306
|
try {
|
|
26035
|
-
if (
|
|
26307
|
+
if (readFileSync6(join8(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
26036
26308
|
} catch {
|
|
26037
26309
|
return;
|
|
26038
26310
|
}
|
|
26039
|
-
|
|
26311
|
+
rmSync5(lease.lock, { recursive: true, force: true });
|
|
26040
26312
|
}
|
|
26041
26313
|
function withRotationLock(keysDir2, work) {
|
|
26042
26314
|
ensureDataDirSync(keysDir2);
|
|
@@ -26053,7 +26325,7 @@ var FileKeyProvider = class {
|
|
|
26053
26325
|
this.#keysDir = keysDir2;
|
|
26054
26326
|
}
|
|
26055
26327
|
get filePath() {
|
|
26056
|
-
return
|
|
26328
|
+
return join8(this.#keysDir, VAULT_KEY_FILENAME);
|
|
26057
26329
|
}
|
|
26058
26330
|
loadOrCreate() {
|
|
26059
26331
|
return asAsync(() => {
|
|
@@ -26083,7 +26355,7 @@ var FileKeyProvider = class {
|
|
|
26083
26355
|
#read() {
|
|
26084
26356
|
let raw;
|
|
26085
26357
|
try {
|
|
26086
|
-
raw =
|
|
26358
|
+
raw = readFileSync6(this.filePath, "utf8");
|
|
26087
26359
|
} catch (err) {
|
|
26088
26360
|
if (err.code === "ENOENT") return null;
|
|
26089
26361
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -26140,7 +26412,7 @@ var FileKeyProvider = class {
|
|
|
26140
26412
|
};
|
|
26141
26413
|
function tightenFileMode(file2) {
|
|
26142
26414
|
try {
|
|
26143
|
-
|
|
26415
|
+
chmodSync3(file2, DATA_FILE_MODE);
|
|
26144
26416
|
} catch {
|
|
26145
26417
|
}
|
|
26146
26418
|
}
|
|
@@ -26405,25 +26677,25 @@ var SecretVault = class {
|
|
|
26405
26677
|
* model. Every call that gets as far as an identified row writes an audit row.
|
|
26406
26678
|
*/
|
|
26407
26679
|
async detokenize(token, opts) {
|
|
26408
|
-
const
|
|
26409
|
-
if (!
|
|
26680
|
+
const parsed2 = parsePointer(token);
|
|
26681
|
+
if (!parsed2) return UNAVAILABLE;
|
|
26410
26682
|
let signKey;
|
|
26411
26683
|
try {
|
|
26412
|
-
const epoch = await this.#keys.materialFor(
|
|
26684
|
+
const epoch = await this.#keys.materialFor(parsed2.keyVersion);
|
|
26413
26685
|
signKey = deriveSubkeys(epoch.material).sign;
|
|
26414
26686
|
} catch {
|
|
26415
26687
|
return UNAVAILABLE;
|
|
26416
26688
|
}
|
|
26417
|
-
if (!verifyPointerTag(signKey,
|
|
26689
|
+
if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
|
|
26418
26690
|
return UNAVAILABLE;
|
|
26419
26691
|
}
|
|
26420
|
-
const pointerId = base32Encode(
|
|
26692
|
+
const pointerId = base32Encode(parsed2.pointerId);
|
|
26421
26693
|
const row = this.#repo.byPointerId(pointerId);
|
|
26422
26694
|
if (!row) {
|
|
26423
26695
|
this.#audit(pointerId, opts, "unavailable");
|
|
26424
26696
|
return UNAVAILABLE;
|
|
26425
26697
|
}
|
|
26426
|
-
if (row.category !==
|
|
26698
|
+
if (row.category !== parsed2.category) return UNAVAILABLE;
|
|
26427
26699
|
if (opts.target === "model") {
|
|
26428
26700
|
const grantId = opts.grantId;
|
|
26429
26701
|
const verify = this.#verifyGrant;
|
|
@@ -26460,7 +26732,7 @@ var SecretVault = class {
|
|
|
26460
26732
|
// moved the epoch past the one this token names, and a format bump may
|
|
26461
26733
|
// have moved the constant past the generation this row was sealed
|
|
26462
26734
|
// under — the AAD follows the row in both cases, never the token.
|
|
26463
|
-
bindingInput(row.keyVersion,
|
|
26735
|
+
bindingInput(row.keyVersion, parsed2.pointerId, row.category, row.formatVersion)
|
|
26464
26736
|
);
|
|
26465
26737
|
} catch {
|
|
26466
26738
|
raw = null;
|
|
@@ -26678,19 +26950,19 @@ var SecretVault = class {
|
|
|
26678
26950
|
// preview. Verifying needs the historical epoch's key, which is why these
|
|
26679
26951
|
// surfaces are async.
|
|
26680
26952
|
async #rowFor(token) {
|
|
26681
|
-
const
|
|
26682
|
-
if (!
|
|
26953
|
+
const parsed2 = parsePointer(token);
|
|
26954
|
+
if (!parsed2) return null;
|
|
26683
26955
|
try {
|
|
26684
|
-
const epoch = await this.#keys.materialFor(
|
|
26956
|
+
const epoch = await this.#keys.materialFor(parsed2.keyVersion);
|
|
26685
26957
|
const signKey = deriveSubkeys(epoch.material).sign;
|
|
26686
|
-
if (!verifyPointerTag(signKey,
|
|
26958
|
+
if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
|
|
26687
26959
|
return null;
|
|
26688
26960
|
}
|
|
26689
26961
|
} catch {
|
|
26690
26962
|
return null;
|
|
26691
26963
|
}
|
|
26692
|
-
const row = this.#repo.byPointerId(base32Encode(
|
|
26693
|
-
if (row?.category !==
|
|
26964
|
+
const row = this.#repo.byPointerId(base32Encode(parsed2.pointerId));
|
|
26965
|
+
if (row?.category !== parsed2.category) return null;
|
|
26694
26966
|
return row;
|
|
26695
26967
|
}
|
|
26696
26968
|
#audit(pointerId, opts, outcome) {
|
|
@@ -26710,13 +26982,13 @@ var SecretVault = class {
|
|
|
26710
26982
|
};
|
|
26711
26983
|
|
|
26712
26984
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
26713
|
-
import { existsSync as
|
|
26714
|
-
import { join as
|
|
26985
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
26986
|
+
import { join as join9 } from "path";
|
|
26715
26987
|
var MARKER = "warn-era-capped";
|
|
26716
26988
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
26717
26989
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
26718
|
-
const marker =
|
|
26719
|
-
if (
|
|
26990
|
+
const marker = join9(dataDir2, MARKER);
|
|
26991
|
+
if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
|
|
26720
26992
|
const capped = db.policies.capCategoryActions();
|
|
26721
26993
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
26722
26994
|
`, { mode: DATA_FILE_MODE });
|
|
@@ -26757,8 +27029,8 @@ function hostOf(url2) {
|
|
|
26757
27029
|
}
|
|
26758
27030
|
}
|
|
26759
27031
|
function resolveProvider() {
|
|
26760
|
-
const
|
|
26761
|
-
const env =
|
|
27032
|
+
const parsed2 = ProviderEnvSchema.safeParse(process.env);
|
|
27033
|
+
const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
|
|
26762
27034
|
if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
|
|
26763
27035
|
if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
|
|
26764
27036
|
const baseUrl = env.ANTHROPIC_BASE_URL;
|
|
@@ -26775,8 +27047,8 @@ function resolveProvider() {
|
|
|
26775
27047
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
26776
27048
|
try {
|
|
26777
27049
|
ensureLayoutDirSync(base);
|
|
26778
|
-
const settingsFile =
|
|
26779
|
-
if (
|
|
27050
|
+
const settingsFile = join10(settingsDir(base), "settings.json");
|
|
27051
|
+
if (existsSync6(settingsFile)) tightenFile(settingsFile);
|
|
26780
27052
|
} catch {
|
|
26781
27053
|
}
|
|
26782
27054
|
migrateLegacyLayout(base);
|
|
@@ -26799,9 +27071,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
26799
27071
|
}
|
|
26800
27072
|
|
|
26801
27073
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
26802
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
27074
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
26803
27075
|
import { homedir as homedir2 } from "os";
|
|
26804
|
-
import { basename as basename3, join as
|
|
27076
|
+
import { basename as basename3, join as join12 } from "path";
|
|
26805
27077
|
|
|
26806
27078
|
// ../../packages/detections/src/egress/registry.ts
|
|
26807
27079
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -28578,10 +28850,10 @@ var localhost_ref_default = {
|
|
|
28578
28850
|
severity: "low",
|
|
28579
28851
|
matcher: {
|
|
28580
28852
|
type: "regex",
|
|
28581
|
-
pattern: "
|
|
28853
|
+
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_])",
|
|
28582
28854
|
flags: "g"
|
|
28583
28855
|
},
|
|
28584
|
-
examples: ["localhost", "127.0.0.1"]
|
|
28856
|
+
examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
|
|
28585
28857
|
};
|
|
28586
28858
|
|
|
28587
28859
|
// ../../rules/core-code-context/stack-trace.json
|
|
@@ -29893,8 +30165,8 @@ function uniqueRuleIds(findings) {
|
|
|
29893
30165
|
}
|
|
29894
30166
|
|
|
29895
30167
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
29896
|
-
import { existsSync as
|
|
29897
|
-
import { basename as basename2, dirname as
|
|
30168
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
|
|
30169
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join11, sep as sep2 } from "path";
|
|
29898
30170
|
function resolveRepo(cwd) {
|
|
29899
30171
|
try {
|
|
29900
30172
|
const root = findGitRoot(cwd);
|
|
@@ -29909,36 +30181,36 @@ function resolveRepo(cwd) {
|
|
|
29909
30181
|
function findGitRoot(start) {
|
|
29910
30182
|
let dir = start;
|
|
29911
30183
|
for (; ; ) {
|
|
29912
|
-
if (
|
|
29913
|
-
const parent =
|
|
30184
|
+
if (existsSync7(join11(dir, ".git"))) return dir;
|
|
30185
|
+
const parent = dirname3(dir);
|
|
29914
30186
|
if (parent === dir) return void 0;
|
|
29915
30187
|
dir = parent;
|
|
29916
30188
|
}
|
|
29917
30189
|
}
|
|
29918
30190
|
function resolveGitContext(root) {
|
|
29919
|
-
const dotGit =
|
|
30191
|
+
const dotGit = join11(root, ".git");
|
|
29920
30192
|
try {
|
|
29921
|
-
if (
|
|
29922
|
-
return { configPath:
|
|
30193
|
+
if (statSync6(dotGit).isDirectory()) {
|
|
30194
|
+
return { configPath: join11(dotGit, "config"), headRoot: root };
|
|
29923
30195
|
}
|
|
29924
30196
|
} catch {
|
|
29925
30197
|
return void 0;
|
|
29926
30198
|
}
|
|
29927
30199
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
29928
30200
|
if (!target) return void 0;
|
|
29929
|
-
const gitdir = isAbsolute(target) ? target :
|
|
29930
|
-
if (
|
|
29931
|
-
return { configPath:
|
|
30201
|
+
const gitdir = isAbsolute(target) ? target : join11(root, target);
|
|
30202
|
+
if (existsSync7(join11(gitdir, "config"))) {
|
|
30203
|
+
return { configPath: join11(gitdir, "config"), headRoot: root };
|
|
29932
30204
|
}
|
|
29933
|
-
const commonRaw = safeRead(
|
|
30205
|
+
const commonRaw = safeRead(join11(gitdir, "commondir"))?.trim();
|
|
29934
30206
|
if (!commonRaw) return void 0;
|
|
29935
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
29936
|
-
const headRoot = basename2(commonGitDir) === ".git" ?
|
|
29937
|
-
return { configPath:
|
|
30207
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join11(gitdir, commonRaw);
|
|
30208
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
|
|
30209
|
+
return { configPath: join11(commonGitDir, "config"), headRoot };
|
|
29938
30210
|
}
|
|
29939
30211
|
function safeRead(path) {
|
|
29940
30212
|
try {
|
|
29941
|
-
return
|
|
30213
|
+
return readFileSync7(path, "utf8");
|
|
29942
30214
|
} catch {
|
|
29943
30215
|
return void 0;
|
|
29944
30216
|
}
|
|
@@ -29999,7 +30271,7 @@ function buildIngestEvent(input) {
|
|
|
29999
30271
|
}
|
|
30000
30272
|
|
|
30001
30273
|
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
30002
|
-
import { existsSync as
|
|
30274
|
+
import { existsSync as existsSync8 } from "fs";
|
|
30003
30275
|
import { fileURLToPath } from "url";
|
|
30004
30276
|
import { Worker } from "worker_threads";
|
|
30005
30277
|
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
@@ -30013,7 +30285,7 @@ function resolveWorkerUrl() {
|
|
|
30013
30285
|
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
30014
30286
|
const candidate = new URL(name, import.meta.url);
|
|
30015
30287
|
try {
|
|
30016
|
-
if (
|
|
30288
|
+
if (existsSync8(fileURLToPath(candidate))) {
|
|
30017
30289
|
resolvedWorkerUrl = candidate;
|
|
30018
30290
|
return candidate;
|
|
30019
30291
|
}
|
|
@@ -30198,8 +30470,8 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30198
30470
|
}
|
|
30199
30471
|
function enqueue(spec) {
|
|
30200
30472
|
const next = chain.then(
|
|
30201
|
-
() => new Promise((
|
|
30202
|
-
spec(
|
|
30473
|
+
() => new Promise((resolve2) => {
|
|
30474
|
+
spec(resolve2);
|
|
30203
30475
|
})
|
|
30204
30476
|
);
|
|
30205
30477
|
chain = next.then(
|
|
@@ -30210,7 +30482,7 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30210
30482
|
}
|
|
30211
30483
|
return {
|
|
30212
30484
|
scan(text, context, scanOpts) {
|
|
30213
|
-
return enqueue((
|
|
30485
|
+
return enqueue((resolve2) => {
|
|
30214
30486
|
runOne(
|
|
30215
30487
|
{
|
|
30216
30488
|
budgetMs,
|
|
@@ -30223,23 +30495,23 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30223
30495
|
}),
|
|
30224
30496
|
reply: (message) => {
|
|
30225
30497
|
if (message.kind !== "result") return false;
|
|
30226
|
-
|
|
30498
|
+
resolve2({ status: "ok", findings: message.findings });
|
|
30227
30499
|
return true;
|
|
30228
30500
|
}
|
|
30229
30501
|
},
|
|
30230
|
-
|
|
30502
|
+
resolve2
|
|
30231
30503
|
);
|
|
30232
30504
|
});
|
|
30233
30505
|
},
|
|
30234
30506
|
probe(rule) {
|
|
30235
|
-
return enqueue((
|
|
30507
|
+
return enqueue((resolve2) => {
|
|
30236
30508
|
runOne(
|
|
30237
30509
|
{
|
|
30238
30510
|
budgetMs: probeBudgetMs,
|
|
30239
30511
|
build: (id) => ({ kind: "probe", id, rule }),
|
|
30240
30512
|
reply: (message) => {
|
|
30241
30513
|
if (message.kind !== "probed") return false;
|
|
30242
|
-
|
|
30514
|
+
resolve2({
|
|
30243
30515
|
status: "ok",
|
|
30244
30516
|
verdict: message.verdict,
|
|
30245
30517
|
worstMs: message.worstMs,
|
|
@@ -30248,7 +30520,7 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
30248
30520
|
return true;
|
|
30249
30521
|
}
|
|
30250
30522
|
},
|
|
30251
|
-
|
|
30523
|
+
resolve2
|
|
30252
30524
|
);
|
|
30253
30525
|
});
|
|
30254
30526
|
},
|
|
@@ -30478,24 +30750,24 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
30478
30750
|
|
|
30479
30751
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
30480
30752
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
30481
|
-
import { readFileSync as
|
|
30482
|
-
import { join as
|
|
30753
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
30754
|
+
import { join as join13 } from "path";
|
|
30483
30755
|
|
|
30484
30756
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
30485
30757
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
30486
30758
|
|
|
30487
30759
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
30488
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
30489
|
-
import { join as
|
|
30760
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
30761
|
+
import { join as join14 } from "path";
|
|
30490
30762
|
var NUDGE_MARKER = "nudge-last-session";
|
|
30491
30763
|
function claimOnboardingNudge(dataDir2, sessionId) {
|
|
30492
30764
|
return claimOncePerSession(dataDir2, NUDGE_MARKER, sessionId);
|
|
30493
30765
|
}
|
|
30494
30766
|
function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
30495
30767
|
if (!sessionId) return true;
|
|
30496
|
-
const path =
|
|
30768
|
+
const path = join14(dataDir2, marker);
|
|
30497
30769
|
try {
|
|
30498
|
-
if (
|
|
30770
|
+
if (readFileSync10(path, "utf8") === sessionId) return false;
|
|
30499
30771
|
} catch {
|
|
30500
30772
|
}
|
|
30501
30773
|
try {
|
|
@@ -30507,12 +30779,12 @@ function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
|
30507
30779
|
}
|
|
30508
30780
|
|
|
30509
30781
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
30510
|
-
import { readdirSync as readdirSync3, realpathSync as
|
|
30511
|
-
import { basename as basename4, dirname as
|
|
30782
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
30783
|
+
import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
|
|
30512
30784
|
|
|
30513
30785
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
30514
|
-
import { existsSync as
|
|
30515
|
-
import { basename as basename5, join as
|
|
30786
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
30787
|
+
import { basename as basename5, join as join15 } from "path";
|
|
30516
30788
|
|
|
30517
30789
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30518
30790
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -30883,8 +31155,8 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30883
31155
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
30884
31156
|
|
|
30885
31157
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
30886
|
-
import { mkdirSync as mkdirSync3, statSync as
|
|
30887
|
-
import { join as
|
|
31158
|
+
import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
31159
|
+
import { join as join16 } from "path";
|
|
30888
31160
|
|
|
30889
31161
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
30890
31162
|
function redactedPlaceholder(category) {
|
|
@@ -31214,9 +31486,9 @@ function commandsFor(platform2) {
|
|
|
31214
31486
|
function writeClipboard(text, opts) {
|
|
31215
31487
|
try {
|
|
31216
31488
|
const platform2 = opts?.platform ?? process.platform;
|
|
31217
|
-
const
|
|
31489
|
+
const spawn2 = opts?.spawn ?? defaultSpawner;
|
|
31218
31490
|
for (const command of commandsFor(platform2)) {
|
|
31219
|
-
if (
|
|
31491
|
+
if (spawn2(command.cmd, [...command.args], text).ok) return true;
|
|
31220
31492
|
}
|
|
31221
31493
|
return false;
|
|
31222
31494
|
} catch {
|
|
@@ -31229,7 +31501,7 @@ var ONBOARDING_NUDGE = "AKA Security is installed but not calibrated \u2014 run
|
|
|
31229
31501
|
|
|
31230
31502
|
// src/hooks/shared.ts
|
|
31231
31503
|
async function readStdin() {
|
|
31232
|
-
return new Promise((
|
|
31504
|
+
return new Promise((resolve2) => {
|
|
31233
31505
|
let data = "";
|
|
31234
31506
|
let settled = false;
|
|
31235
31507
|
const finish = () => {
|
|
@@ -31238,7 +31510,7 @@ async function readStdin() {
|
|
|
31238
31510
|
clearTimeout(timer);
|
|
31239
31511
|
process.stdin.removeListener("data", onData);
|
|
31240
31512
|
process.stdin.removeListener("end", finish);
|
|
31241
|
-
|
|
31513
|
+
resolve2(data);
|
|
31242
31514
|
};
|
|
31243
31515
|
const onData = (chunk) => {
|
|
31244
31516
|
data += chunk;
|
|
@@ -31252,8 +31524,8 @@ async function readStdin() {
|
|
|
31252
31524
|
}
|
|
31253
31525
|
function parseJson(raw) {
|
|
31254
31526
|
try {
|
|
31255
|
-
const
|
|
31256
|
-
return typeof
|
|
31527
|
+
const parsed2 = JSON.parse(raw);
|
|
31528
|
+
return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
|
|
31257
31529
|
} catch {
|
|
31258
31530
|
return null;
|
|
31259
31531
|
}
|
|
@@ -31263,12 +31535,12 @@ function getString(record2, key) {
|
|
|
31263
31535
|
return typeof value === "string" ? value : void 0;
|
|
31264
31536
|
}
|
|
31265
31537
|
function emit(output) {
|
|
31266
|
-
return new Promise((
|
|
31538
|
+
return new Promise((resolve2) => {
|
|
31267
31539
|
let settled = false;
|
|
31268
31540
|
const finish = () => {
|
|
31269
31541
|
if (settled) return;
|
|
31270
31542
|
settled = true;
|
|
31271
|
-
|
|
31543
|
+
resolve2();
|
|
31272
31544
|
};
|
|
31273
31545
|
process.stdout.on("error", finish);
|
|
31274
31546
|
process.stdout.write(JSON.stringify(output), finish);
|
|
@@ -31284,61 +31556,1251 @@ function baseMetadata(input) {
|
|
|
31284
31556
|
}
|
|
31285
31557
|
|
|
31286
31558
|
// src/hooks/store-health.ts
|
|
31287
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
31288
|
-
import { join as
|
|
31559
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
31560
|
+
import { dirname as dirname5, join as join22 } from "path";
|
|
31561
|
+
|
|
31562
|
+
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
31563
|
+
function statusOf(err) {
|
|
31564
|
+
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
31565
|
+
const { status } = err;
|
|
31566
|
+
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
31567
|
+
return status >= 100 && status <= 599 ? status : null;
|
|
31568
|
+
}
|
|
31569
|
+
function classifyFailure(err) {
|
|
31570
|
+
switch (statusOf(err)) {
|
|
31571
|
+
case 401:
|
|
31572
|
+
return "unauthorized";
|
|
31573
|
+
case 403:
|
|
31574
|
+
return "forbidden";
|
|
31575
|
+
default:
|
|
31576
|
+
return "unreachable";
|
|
31577
|
+
}
|
|
31578
|
+
}
|
|
31289
31579
|
|
|
31290
|
-
// ../../packages/plugin-runtime/src/
|
|
31291
|
-
import {
|
|
31580
|
+
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
31581
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
31582
|
+
import { join as join17 } from "path";
|
|
31583
|
+
var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
31584
|
+
function forwardDropsPath(dataDir2) {
|
|
31585
|
+
return join17(dataDir2, FORWARD_DROPS_FILENAME);
|
|
31586
|
+
}
|
|
31587
|
+
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
31588
|
+
if (count <= 0) return;
|
|
31589
|
+
try {
|
|
31590
|
+
ensureDataDirSync(dataDir2);
|
|
31591
|
+
const previous = readForwardDrops(dataDir2);
|
|
31592
|
+
const next = {
|
|
31593
|
+
droppedForwards: (previous?.droppedForwards ?? 0) + count,
|
|
31594
|
+
lastDropAtMs: nowMs
|
|
31595
|
+
};
|
|
31596
|
+
writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
|
|
31597
|
+
`);
|
|
31598
|
+
} catch {
|
|
31599
|
+
}
|
|
31600
|
+
}
|
|
31601
|
+
function readForwardDrops(dataDir2) {
|
|
31602
|
+
try {
|
|
31603
|
+
const parsed2 = JSON.parse(readFileSync11(forwardDropsPath(dataDir2), "utf8"));
|
|
31604
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
31605
|
+
const record2 = parsed2;
|
|
31606
|
+
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
31607
|
+
return null;
|
|
31608
|
+
}
|
|
31609
|
+
if (record2.droppedForwards <= 0) return null;
|
|
31610
|
+
if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
|
|
31611
|
+
return null;
|
|
31612
|
+
}
|
|
31613
|
+
return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
|
|
31614
|
+
} catch {
|
|
31615
|
+
return null;
|
|
31616
|
+
}
|
|
31617
|
+
}
|
|
31292
31618
|
|
|
31293
|
-
// ../../packages/plugin-runtime/src/
|
|
31294
|
-
|
|
31619
|
+
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
31620
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
31621
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
31622
|
+
import { readFile, rename, writeFile } from "fs/promises";
|
|
31623
|
+
import { join as join18 } from "path";
|
|
31624
|
+
|
|
31625
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
31626
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
31627
|
+
function withTimeout(promise2, ms) {
|
|
31628
|
+
let timer;
|
|
31629
|
+
const timeout = new Promise((_, reject) => {
|
|
31630
|
+
timer = setTimeout(() => {
|
|
31631
|
+
reject(new Error("attached gateway request timed out"));
|
|
31632
|
+
}, ms);
|
|
31633
|
+
});
|
|
31634
|
+
promise2.catch(() => void 0);
|
|
31635
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
31636
|
+
clearTimeout(timer);
|
|
31637
|
+
});
|
|
31638
|
+
}
|
|
31295
31639
|
|
|
31296
|
-
// ../../packages/plugin-runtime/src/
|
|
31297
|
-
|
|
31298
|
-
|
|
31299
|
-
|
|
31300
|
-
|
|
31301
|
-
|
|
31302
|
-
|
|
31303
|
-
|
|
31304
|
-
|
|
31305
|
-
|
|
31306
|
-
|
|
31307
|
-
|
|
31308
|
-
|
|
31309
|
-
|
|
31310
|
-
|
|
31640
|
+
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
31641
|
+
function isInvalidRequest(err) {
|
|
31642
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
31643
|
+
}
|
|
31644
|
+
var FORWARD_BUDGET_MS = 1500;
|
|
31645
|
+
var DECISION_PATH_BUDGET_MS = 800;
|
|
31646
|
+
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
31647
|
+
var BREAKER_COOLDOWN_MS = 3e4;
|
|
31648
|
+
var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
|
|
31649
|
+
var FAILURES = /* @__PURE__ */ new Set([
|
|
31650
|
+
"unauthorized",
|
|
31651
|
+
"forbidden",
|
|
31652
|
+
"unreachable"
|
|
31653
|
+
]);
|
|
31654
|
+
var FORWARD_STATE_FILENAME = "attached-state.json";
|
|
31655
|
+
var STATE_FILENAME = FORWARD_STATE_FILENAME;
|
|
31656
|
+
function parseBreakerState(raw, nowMs) {
|
|
31657
|
+
try {
|
|
31658
|
+
const parsed2 = JSON.parse(raw);
|
|
31659
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
31660
|
+
const record2 = parsed2;
|
|
31661
|
+
const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
|
|
31662
|
+
const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
|
|
31663
|
+
const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
|
|
31664
|
+
return { consecutiveFailures: failures, openedAtMs, lastFailure };
|
|
31665
|
+
} catch {
|
|
31666
|
+
return null;
|
|
31311
31667
|
}
|
|
31312
|
-
|
|
31313
|
-
|
|
31668
|
+
}
|
|
31669
|
+
function createForwardPolicy(deps) {
|
|
31670
|
+
const now = deps.now ?? (() => Date.now());
|
|
31671
|
+
const file2 = join18(deps.dir, STATE_FILENAME);
|
|
31672
|
+
let state = null;
|
|
31673
|
+
let loading = null;
|
|
31674
|
+
async function readState() {
|
|
31675
|
+
let raw;
|
|
31676
|
+
try {
|
|
31677
|
+
raw = await readFile(file2, "utf8");
|
|
31678
|
+
} catch {
|
|
31679
|
+
return { ...CLOSED };
|
|
31680
|
+
}
|
|
31681
|
+
return parseBreakerState(raw, now()) ?? { ...CLOSED };
|
|
31314
31682
|
}
|
|
31315
|
-
|
|
31316
|
-
|
|
31317
|
-
|
|
31683
|
+
async function load() {
|
|
31684
|
+
if (state !== null) return state;
|
|
31685
|
+
loading ??= readState().then((loaded) => {
|
|
31686
|
+
state = loaded;
|
|
31687
|
+
loading = null;
|
|
31688
|
+
return loaded;
|
|
31689
|
+
});
|
|
31690
|
+
return loading;
|
|
31318
31691
|
}
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
|
|
31325
|
-
|
|
31692
|
+
async function persist(next) {
|
|
31693
|
+
state = next;
|
|
31694
|
+
try {
|
|
31695
|
+
await ensureDataDir(deps.dir);
|
|
31696
|
+
const tmp = `${file2}.${randomUUID15()}.tmp`;
|
|
31697
|
+
await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
31698
|
+
await rename(tmp, file2);
|
|
31699
|
+
} catch {
|
|
31700
|
+
}
|
|
31326
31701
|
}
|
|
31327
|
-
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
if (inputs.length === 0) return Promise.resolve();
|
|
31332
|
-
return new Promise((resolve, reject) => {
|
|
31702
|
+
return {
|
|
31703
|
+
async run(op, opts) {
|
|
31704
|
+
const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
|
|
31705
|
+
let current;
|
|
31333
31706
|
try {
|
|
31334
|
-
|
|
31335
|
-
|
|
31707
|
+
current = await load();
|
|
31708
|
+
} catch {
|
|
31709
|
+
current = { ...CLOSED };
|
|
31710
|
+
}
|
|
31711
|
+
const at = now();
|
|
31712
|
+
if (current.openedAtMs !== null) {
|
|
31713
|
+
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
31714
|
+
return { ok: false, reason: "breaker-open" };
|
|
31715
|
+
}
|
|
31716
|
+
await persist({
|
|
31717
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
31718
|
+
openedAtMs: at,
|
|
31719
|
+
lastFailure: current.lastFailure
|
|
31336
31720
|
});
|
|
31337
|
-
resolve();
|
|
31338
|
-
} catch (err) {
|
|
31339
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
31340
31721
|
}
|
|
31341
|
-
|
|
31722
|
+
try {
|
|
31723
|
+
const value = await withTimeout(op(), budget);
|
|
31724
|
+
if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
|
|
31725
|
+
await persist({ ...CLOSED });
|
|
31726
|
+
}
|
|
31727
|
+
return { ok: true, value };
|
|
31728
|
+
} catch (err) {
|
|
31729
|
+
if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
|
|
31730
|
+
const reason = classifyFailure(err);
|
|
31731
|
+
const failures = current.consecutiveFailures + 1;
|
|
31732
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
31733
|
+
await persist({
|
|
31734
|
+
consecutiveFailures: failures,
|
|
31735
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
31736
|
+
lastFailure: reason
|
|
31737
|
+
});
|
|
31738
|
+
return { ok: false, reason };
|
|
31739
|
+
}
|
|
31740
|
+
}
|
|
31741
|
+
};
|
|
31742
|
+
}
|
|
31743
|
+
|
|
31744
|
+
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
31745
|
+
var ACTION_STRENGTH = {
|
|
31746
|
+
allow: 0,
|
|
31747
|
+
log: 1,
|
|
31748
|
+
warn: 2,
|
|
31749
|
+
redact: 3,
|
|
31750
|
+
block: 4
|
|
31751
|
+
};
|
|
31752
|
+
function ruleCategoryMap(wireRules, localRules) {
|
|
31753
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
31754
|
+
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
31755
|
+
for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
|
|
31756
|
+
for (const pack of bundledDetections()) {
|
|
31757
|
+
for (const rule of pack.rules) map2.set(rule.id, rule.category);
|
|
31758
|
+
}
|
|
31759
|
+
return map2;
|
|
31760
|
+
}
|
|
31761
|
+
function strongerOf(a, b) {
|
|
31762
|
+
if (a === null) return b;
|
|
31763
|
+
if (b === null) return a;
|
|
31764
|
+
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
31765
|
+
}
|
|
31766
|
+
function policyKey(policy) {
|
|
31767
|
+
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
31768
|
+
}
|
|
31769
|
+
function floorFor(policy, categoryByRuleId) {
|
|
31770
|
+
const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
|
|
31771
|
+
return category === void 0 ? null : DEFAULT_ACTIONS[category];
|
|
31772
|
+
}
|
|
31773
|
+
function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
31774
|
+
const merged = /* @__PURE__ */ new Map();
|
|
31775
|
+
const disabled = [];
|
|
31776
|
+
const remoteCategoryAction = /* @__PURE__ */ new Map();
|
|
31777
|
+
for (const policy of remotePolicies) {
|
|
31778
|
+
if (!policy.enabled) continue;
|
|
31779
|
+
if (!("category" in policy.target)) continue;
|
|
31780
|
+
if (remoteCategoryAction.has(policy.target.category)) continue;
|
|
31781
|
+
const floor = floorFor(policy, categoryByRuleId);
|
|
31782
|
+
remoteCategoryAction.set(
|
|
31783
|
+
policy.target.category,
|
|
31784
|
+
floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
|
|
31785
|
+
);
|
|
31786
|
+
}
|
|
31787
|
+
for (const policy of localPolicies) {
|
|
31788
|
+
if (!policy.enabled) {
|
|
31789
|
+
disabled.push(policy);
|
|
31790
|
+
continue;
|
|
31791
|
+
}
|
|
31792
|
+
const key = policyKey(policy);
|
|
31793
|
+
if (merged.has(key)) continue;
|
|
31794
|
+
let remoteFloor = null;
|
|
31795
|
+
if ("ruleId" in policy.target) {
|
|
31796
|
+
const category = categoryByRuleId.get(policy.target.ruleId);
|
|
31797
|
+
if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
|
|
31798
|
+
}
|
|
31799
|
+
merged.set(
|
|
31800
|
+
key,
|
|
31801
|
+
remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
|
|
31802
|
+
);
|
|
31803
|
+
}
|
|
31804
|
+
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
31805
|
+
for (const policy of merged.values()) {
|
|
31806
|
+
if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
|
|
31807
|
+
}
|
|
31808
|
+
for (const policy of remotePolicies) {
|
|
31809
|
+
if (!policy.enabled) {
|
|
31810
|
+
disabled.push(policy);
|
|
31811
|
+
continue;
|
|
31812
|
+
}
|
|
31813
|
+
const key = policyKey(policy);
|
|
31814
|
+
const floor = floorFor(policy, categoryByRuleId);
|
|
31815
|
+
let localFloor = null;
|
|
31816
|
+
if ("ruleId" in policy.target) {
|
|
31817
|
+
const category = categoryByRuleId.get(policy.target.ruleId);
|
|
31818
|
+
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
31819
|
+
}
|
|
31820
|
+
const effectiveFloor = strongerOf(floor, localFloor);
|
|
31821
|
+
const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
|
|
31822
|
+
const existing = merged.get(key);
|
|
31823
|
+
if (existing === void 0) {
|
|
31824
|
+
merged.set(key, clamped);
|
|
31825
|
+
continue;
|
|
31826
|
+
}
|
|
31827
|
+
if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
|
|
31828
|
+
merged.set(key, clamped);
|
|
31829
|
+
}
|
|
31830
|
+
}
|
|
31831
|
+
return [...merged.values(), ...disabled];
|
|
31832
|
+
}
|
|
31833
|
+
var AttachedDataGateway = class {
|
|
31834
|
+
constructor(deps) {
|
|
31835
|
+
this.deps = deps;
|
|
31836
|
+
}
|
|
31837
|
+
deps;
|
|
31838
|
+
/**
|
|
31839
|
+
* The control plane's OWN resolution of this session's inventory, captured by
|
|
31840
|
+
* ensureInventory. Null until the first successful forward — and it stays
|
|
31841
|
+
* null for the whole session when the control plane is unreachable, which is fine:
|
|
31842
|
+
* reKeyForForward then leaves the event's ids alone and the control plane resolves
|
|
31843
|
+
* what it can from the descriptors it already has.
|
|
31844
|
+
*/
|
|
31845
|
+
remoteInventory = null;
|
|
31846
|
+
// ---------------------------------------------------------------------
|
|
31847
|
+
// Writes: local first, then forward.
|
|
31848
|
+
// ---------------------------------------------------------------------
|
|
31849
|
+
async recordCapture(record2) {
|
|
31850
|
+
await this.deps.local.recordCapture(record2);
|
|
31851
|
+
await this.deps.forward.run(
|
|
31852
|
+
() => this.deps.client.ingestEvents({
|
|
31853
|
+
events: [record2.event],
|
|
31854
|
+
...record2.dedupe ? { dedupe: record2.dedupe } : {}
|
|
31855
|
+
}),
|
|
31856
|
+
{ decisionPath: true }
|
|
31857
|
+
);
|
|
31858
|
+
}
|
|
31859
|
+
async ensureInventory(ctx) {
|
|
31860
|
+
const resolved = await this.deps.local.ensureInventory(ctx);
|
|
31861
|
+
const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
|
|
31862
|
+
this.remoteInventory = remote.ok ? remote.value : null;
|
|
31863
|
+
const snapshot = await (async () => {
|
|
31864
|
+
try {
|
|
31865
|
+
return await this.deps.posture?.prepare() ?? null;
|
|
31866
|
+
} catch {
|
|
31867
|
+
return null;
|
|
31868
|
+
}
|
|
31869
|
+
})();
|
|
31870
|
+
if (snapshot) {
|
|
31871
|
+
try {
|
|
31872
|
+
await withTimeout(
|
|
31873
|
+
this.deps.posture?.send(snapshot) ?? Promise.resolve(),
|
|
31874
|
+
REQUEST_TIMEOUT_MS
|
|
31875
|
+
);
|
|
31876
|
+
} catch {
|
|
31877
|
+
}
|
|
31878
|
+
}
|
|
31879
|
+
return resolved;
|
|
31880
|
+
}
|
|
31881
|
+
// The id is minted CLIENT-side and stored verbatim: the control plane does NOT
|
|
31882
|
+
// re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
|
|
31883
|
+
// its own scoping columns, so the device and the forwarded copy
|
|
31884
|
+
// share one id space — which is what makes a re-post idempotent at all.
|
|
31885
|
+
//
|
|
31886
|
+
// Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
|
|
31887
|
+
// `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
|
|
31888
|
+
// what makes an attached retry safe: a capture-stubbed session row can still
|
|
31889
|
+
// be HEALED by the authoritative root, while a duplicate non-session event —
|
|
31890
|
+
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
31891
|
+
async recordAuditEvent(event) {
|
|
31892
|
+
await this.deps.local.recordAuditEvent(event);
|
|
31893
|
+
await this.deps.forward.run(
|
|
31894
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
31895
|
+
);
|
|
31896
|
+
}
|
|
31897
|
+
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
31898
|
+
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
31899
|
+
// client method yet) by pre-building the audit event from the natural key.
|
|
31900
|
+
// The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
|
|
31901
|
+
// which would write the event to the local store a second time.
|
|
31902
|
+
async recordLlmCall(input) {
|
|
31903
|
+
await this.deps.local.recordLlmCall(input);
|
|
31904
|
+
await this.deps.forward.run(
|
|
31905
|
+
() => this.deps.client.recordAuditEvent(
|
|
31906
|
+
reKeyForForward(llmAuditEvent(input), this.remoteInventory)
|
|
31907
|
+
)
|
|
31908
|
+
);
|
|
31909
|
+
}
|
|
31910
|
+
/**
|
|
31911
|
+
* Forward one batch, item by item, under ONE aggregate deadline.
|
|
31912
|
+
*
|
|
31913
|
+
* Per-item budgets bound each request and nothing bounded their sum — see
|
|
31914
|
+
* BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
|
|
31915
|
+
* rather than sent: the local write has already succeeded, so every caller
|
|
31916
|
+
* has a correct result to return, and a drop is the outcome this path is
|
|
31917
|
+
* built to accept (G8) where a blown hook timeout is not.
|
|
31918
|
+
*
|
|
31919
|
+
* Serial rather than concurrent on purpose. Firing N requests at once would
|
|
31920
|
+
* trade a latency problem for a burst the plane's own per-key rate limiting
|
|
31921
|
+
* would answer with the refusals the breaker then counts.
|
|
31922
|
+
*
|
|
31923
|
+
* WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
|
|
31924
|
+
* `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
|
|
31925
|
+
* lets status call the forward unhealthy; this path returns BEFORE `run` is
|
|
31926
|
+
* reached, so without the tally in `forward-drops.ts` a slow-but-answering
|
|
31927
|
+
* plane produces no failures, keeps the breaker closed, renders a healthy
|
|
31928
|
+
* block, and discards the tail of every batch indefinitely.
|
|
31929
|
+
*/
|
|
31930
|
+
async forwardBatch(inputs, toEvent) {
|
|
31931
|
+
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
31932
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
31933
|
+
const now = Date.now();
|
|
31934
|
+
if (now >= deadline) {
|
|
31935
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
31936
|
+
return;
|
|
31937
|
+
}
|
|
31938
|
+
const input = inputs[i];
|
|
31939
|
+
await this.deps.forward.run(
|
|
31940
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
|
|
31941
|
+
);
|
|
31942
|
+
}
|
|
31943
|
+
}
|
|
31944
|
+
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
31945
|
+
// gateway may write the whole batch in one local transaction, and looping
|
|
31946
|
+
// here would replace that with N separate local writes.
|
|
31947
|
+
async recordLlmCalls(inputs) {
|
|
31948
|
+
await this.deps.local.recordLlmCalls(inputs);
|
|
31949
|
+
await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
|
|
31950
|
+
}
|
|
31951
|
+
// `input.inspections` (secrets detected client-side in the tool's masked
|
|
31952
|
+
// target) ride along on the request's `inspections` field — the control plane
|
|
31953
|
+
// persists each as an inspection_findings row linked to this audit event
|
|
31954
|
+
// (see RecordAuditEventRequest in @akasecurity/schema). The masked
|
|
31955
|
+
// `target` already rides `input.attributes`, so no raw secret leaks either
|
|
31956
|
+
// way — this only stops the FINDING row itself from being dropped.
|
|
31957
|
+
async recordToolCalls(inputs) {
|
|
31958
|
+
await this.deps.local.recordToolCalls(inputs);
|
|
31959
|
+
await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
|
|
31960
|
+
}
|
|
31961
|
+
// Forwarded as a `config_scan` audit event: there is no dedicated
|
|
31962
|
+
// config-scan ingest endpoint, and the audit-event door is the one the
|
|
31963
|
+
// control plane already opens for client-minted, idempotent records.
|
|
31964
|
+
//
|
|
31965
|
+
// ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
|
|
31966
|
+
// re-derive the rest. A `ConfigScanRecord` is four things committed together
|
|
31967
|
+
// locally — the inventory `items`, this audit event, and the posture
|
|
31968
|
+
// `definitions`/`findings` that reference it — and three of them stay on the
|
|
31969
|
+
// device. Say that plainly rather than let the asymmetry with `recordCapture`
|
|
31970
|
+
// read as the same argument: there, findings are omitted BECAUSE the plane
|
|
31971
|
+
// re-derives them from `Event.content`; here there is no content to re-derive
|
|
31972
|
+
// from, so what is omitted is simply not sent.
|
|
31973
|
+
//
|
|
31974
|
+
// That is the wire contract as it stands rather than an oversight to patch
|
|
31975
|
+
// here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
|
|
31976
|
+
// is documented as tool-call findings — widening it to carry config-scan
|
|
31977
|
+
// findings is an egress change (a posture finding's `maskedMatch` holds the
|
|
31978
|
+
// matched command) and a decision about what an attached deployment is
|
|
31979
|
+
// entitled to, not a bug fix. An attached machine's config posture therefore
|
|
31980
|
+
// reaches the plane as the event only; the dashboard's own view of it is the
|
|
31981
|
+
// local store.
|
|
31982
|
+
async recordConfigScan(record2) {
|
|
31983
|
+
await this.deps.local.recordConfigScan(record2);
|
|
31984
|
+
await this.deps.forward.run(
|
|
31985
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
31986
|
+
);
|
|
31987
|
+
}
|
|
31988
|
+
async recordBlockedDetection(entry) {
|
|
31989
|
+
return this.deps.local.recordBlockedDetection(entry);
|
|
31990
|
+
}
|
|
31991
|
+
/**
|
|
31992
|
+
* LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
|
|
31993
|
+
* with no egress ingest endpoint, so there is nothing to forward to; adding a
|
|
31994
|
+
* forward here would be inventing a wire contract that does not exist. The
|
|
31995
|
+
* local write is the whole operation, and its summary is the real one — the
|
|
31996
|
+
* scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
|
|
31997
|
+
* returning the inner gateway's result keeps the retry semantics honest.
|
|
31998
|
+
*/
|
|
31999
|
+
async recordProjectEgress(input) {
|
|
32000
|
+
return this.deps.local.recordProjectEgress(input);
|
|
32001
|
+
}
|
|
32002
|
+
// ---------------------------------------------------------------------
|
|
32003
|
+
// Reads and device-local ledgers: pure delegation.
|
|
32004
|
+
// ---------------------------------------------------------------------
|
|
32005
|
+
async configInventoryReport() {
|
|
32006
|
+
return this.deps.local.configInventoryReport();
|
|
32007
|
+
}
|
|
32008
|
+
async readSessionProvider(sessionId) {
|
|
32009
|
+
return this.deps.local.readSessionProvider(sessionId);
|
|
32010
|
+
}
|
|
32011
|
+
async facets() {
|
|
32012
|
+
return this.deps.local.facets();
|
|
32013
|
+
}
|
|
32014
|
+
/**
|
|
32015
|
+
* Delegated UNMODIFIED — including its refusals.
|
|
32016
|
+
*
|
|
32017
|
+
* This is a fail-secure boundary: it decides whether an approved exception
|
|
32018
|
+
* lets a blocked action through. Under local-first the local store owns the
|
|
32019
|
+
* exception ledger, so the honest answer is whatever it says; wrapping this
|
|
32020
|
+
* in a fallback (`catch { return true }`, or defaulting on a timeout) would
|
|
32021
|
+
* turn a store error into a granted bypass. If the inner gateway rejects,
|
|
32022
|
+
* this rejects, and the runtime's own handling decides — which is asserted
|
|
32023
|
+
* end-to-end through runtime.capture rather than here.
|
|
32024
|
+
*/
|
|
32025
|
+
async consumeException(id) {
|
|
32026
|
+
return this.deps.local.consumeException(id);
|
|
32027
|
+
}
|
|
32028
|
+
async recentFindings(opts) {
|
|
32029
|
+
return this.deps.local.recentFindings(opts);
|
|
32030
|
+
}
|
|
32031
|
+
async healthSummary() {
|
|
32032
|
+
return this.deps.local.healthSummary();
|
|
32033
|
+
}
|
|
32034
|
+
async activityByDay(days) {
|
|
32035
|
+
return this.deps.local.activityByDay(days);
|
|
32036
|
+
}
|
|
32037
|
+
async tokenReports() {
|
|
32038
|
+
return this.deps.local.tokenReports();
|
|
32039
|
+
}
|
|
32040
|
+
async knownContentHashes() {
|
|
32041
|
+
return this.deps.local.knownContentHashes();
|
|
32042
|
+
}
|
|
32043
|
+
async scanLedger(rulesetHash) {
|
|
32044
|
+
return this.deps.local.scanLedger(rulesetHash);
|
|
32045
|
+
}
|
|
32046
|
+
async recordScanned(entries) {
|
|
32047
|
+
return this.deps.local.recordScanned(entries);
|
|
32048
|
+
}
|
|
32049
|
+
async getRuleProbeVerdict(ruleKey) {
|
|
32050
|
+
return this.deps.local.getRuleProbeVerdict(ruleKey);
|
|
32051
|
+
}
|
|
32052
|
+
async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2) {
|
|
32053
|
+
return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2);
|
|
32054
|
+
}
|
|
32055
|
+
async openAtRestKeysForPath(path) {
|
|
32056
|
+
return this.deps.local.openAtRestKeysForPath(path);
|
|
32057
|
+
}
|
|
32058
|
+
async resolvedAtRestKeysForPath(path) {
|
|
32059
|
+
return this.deps.local.resolvedAtRestKeysForPath(path);
|
|
32060
|
+
}
|
|
32061
|
+
async insertResolution(input) {
|
|
32062
|
+
return this.deps.local.insertResolution(input);
|
|
32063
|
+
}
|
|
32064
|
+
async close() {
|
|
32065
|
+
return this.deps.local.close();
|
|
32066
|
+
}
|
|
32067
|
+
// ---------------------------------------------------------------------
|
|
32068
|
+
// Policy
|
|
32069
|
+
// ---------------------------------------------------------------------
|
|
32070
|
+
async getPolicyBundle() {
|
|
32071
|
+
const local = await this.deps.local.getPolicyBundle();
|
|
32072
|
+
const cached2 = await (async () => {
|
|
32073
|
+
try {
|
|
32074
|
+
return await this.deps.readCachedBundle();
|
|
32075
|
+
} catch {
|
|
32076
|
+
return null;
|
|
32077
|
+
}
|
|
32078
|
+
})();
|
|
32079
|
+
if (cached2 === null) return local;
|
|
32080
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
32081
|
+
for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
|
|
32082
|
+
if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
|
|
32083
|
+
}
|
|
32084
|
+
const rules = [...byRuleId.values()];
|
|
32085
|
+
return {
|
|
32086
|
+
...local,
|
|
32087
|
+
// The remote version identifies the composed bundle for the poller.
|
|
32088
|
+
version: cached2.version,
|
|
32089
|
+
rules,
|
|
32090
|
+
policies: mergeRaiseOnly(
|
|
32091
|
+
local.policies,
|
|
32092
|
+
cached2.policies,
|
|
32093
|
+
ruleCategoryMap(cached2.rules, local.rules)
|
|
32094
|
+
),
|
|
32095
|
+
customKeywords: [...local.customKeywords, ...cached2.customKeywords]
|
|
32096
|
+
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
32097
|
+
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
32098
|
+
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
32099
|
+
// anything able to write policy-cache.json, a kill-switch over the
|
|
32100
|
+
// compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
|
|
32101
|
+
// zero local detection. Spread from `local` above, and deliberately not
|
|
32102
|
+
// re-read from `cached` here.
|
|
32103
|
+
//
|
|
32104
|
+
// THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
|
|
32105
|
+
// and each named here so a reader can tell a decision from an omission:
|
|
32106
|
+
//
|
|
32107
|
+
// `exceptions` — an exception SUPPRESSES a detection, so honoring
|
|
32108
|
+
// one from an unsigned on-disk cache would let
|
|
32109
|
+
// anything able to write that file turn rules off.
|
|
32110
|
+
// Every other field this merge accepts can only
|
|
32111
|
+
// RAISE enforcement; this is the one that cannot,
|
|
32112
|
+
// so it stays local-only until the bundle is
|
|
32113
|
+
// signed. Exceptions remain a device-local ledger.
|
|
32114
|
+
// `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
|
|
32115
|
+
// recoverable, which is a CUSTODY change: it puts
|
|
32116
|
+
// the detected value in the local vault instead of
|
|
32117
|
+
// destroying it. Taking that instruction from the
|
|
32118
|
+
// cache would let a remote party turn one-way
|
|
32119
|
+
// redaction into retention. Dropping it keeps the
|
|
32120
|
+
// one-way behaviour, which the schema itself calls
|
|
32121
|
+
// "the safe direction to default".
|
|
32122
|
+
// `ruleVersions` — remote rules fall back to their own spec version.
|
|
32123
|
+
// Cosmetic rather than protective: it only affects
|
|
32124
|
+
// how a finding is version-attributed, and the two
|
|
32125
|
+
// sides may therefore attribute org rules
|
|
32126
|
+
// differently. Worth carrying once there is a
|
|
32127
|
+
// reader that needs it; nothing reads it today.
|
|
32128
|
+
};
|
|
32129
|
+
}
|
|
32130
|
+
// ---------------------------------------------------------------------
|
|
32131
|
+
// LocalStoreMaintenance — by delegation (D3).
|
|
32132
|
+
//
|
|
32133
|
+
// Implementing these is what actually closes the skipped-local-maintenance
|
|
32134
|
+
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
32135
|
+
// any object carrying all five, so the composite qualifies and SessionStart
|
|
32136
|
+
// runs maintenance on the device's real store.
|
|
32137
|
+
//
|
|
32138
|
+
// ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
32139
|
+
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
32140
|
+
// return value directly; declaring them `async` here would hand those call
|
|
32141
|
+
// sites a Promise and silently break both.
|
|
32142
|
+
// ---------------------------------------------------------------------
|
|
32143
|
+
async sweepTerminalExceptions(retentionMs) {
|
|
32144
|
+
return this.deps.local.sweepTerminalExceptions(retentionMs);
|
|
32145
|
+
}
|
|
32146
|
+
capWarnEraEnforcement(policyMode) {
|
|
32147
|
+
return this.deps.local.capWarnEraEnforcement(policyMode);
|
|
32148
|
+
}
|
|
32149
|
+
async recordProjectFiles(projectId, scan2) {
|
|
32150
|
+
return this.deps.local.recordProjectFiles(projectId, scan2);
|
|
32151
|
+
}
|
|
32152
|
+
async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
|
|
32153
|
+
return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
|
|
32154
|
+
}
|
|
32155
|
+
staleBinaryNotice(currentVersion) {
|
|
32156
|
+
return this.deps.local.staleBinaryNotice(currentVersion);
|
|
32157
|
+
}
|
|
32158
|
+
};
|
|
32159
|
+
function reKeyForForward(event, remote) {
|
|
32160
|
+
if (remote === null) {
|
|
32161
|
+
const stripped = { ...event };
|
|
32162
|
+
delete stripped.hostId;
|
|
32163
|
+
delete stripped.harnessId;
|
|
32164
|
+
delete stripped.sourceProjectId;
|
|
32165
|
+
return stripped;
|
|
32166
|
+
}
|
|
32167
|
+
const rekeyed = { ...event };
|
|
32168
|
+
delete rekeyed.hostId;
|
|
32169
|
+
delete rekeyed.harnessId;
|
|
32170
|
+
delete rekeyed.sourceProjectId;
|
|
32171
|
+
if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
|
|
32172
|
+
if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
|
|
32173
|
+
if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
|
|
32174
|
+
return rekeyed;
|
|
32175
|
+
}
|
|
32176
|
+
var BATCH_FORWARD_BUDGET_MS = 3e3;
|
|
32177
|
+
function llmAuditEvent(input) {
|
|
32178
|
+
return {
|
|
32179
|
+
id: llmCallId(input.sessionId, input.messageId),
|
|
32180
|
+
eventType: "llm_call",
|
|
32181
|
+
startedAt: input.startedAt,
|
|
32182
|
+
parentId: input.parentId,
|
|
32183
|
+
rootSessionId: input.rootSessionId,
|
|
32184
|
+
attributes: input.attributes
|
|
32185
|
+
};
|
|
32186
|
+
}
|
|
32187
|
+
function toolAuditEvent(input) {
|
|
32188
|
+
return {
|
|
32189
|
+
id: toolCallId(input.sessionId, input.toolUseId),
|
|
32190
|
+
eventType: "tool_call",
|
|
32191
|
+
startedAt: input.startedAt,
|
|
32192
|
+
parentId: input.parentId,
|
|
32193
|
+
rootSessionId: input.rootSessionId,
|
|
32194
|
+
attributes: input.attributes,
|
|
32195
|
+
inspections: input.inspections
|
|
32196
|
+
};
|
|
32197
|
+
}
|
|
32198
|
+
|
|
32199
|
+
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
32200
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
32201
|
+
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
32202
|
+
import { join as join19 } from "path";
|
|
32203
|
+
|
|
32204
|
+
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
32205
|
+
import { rename as rename2 } from "fs/promises";
|
|
32206
|
+
var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
32207
|
+
var ATTEMPTS = 5;
|
|
32208
|
+
var delay = (ms) => new Promise((resolve2) => {
|
|
32209
|
+
setTimeout(resolve2, ms);
|
|
32210
|
+
});
|
|
32211
|
+
async function publishByRename(tmp, file2, move = rename2) {
|
|
32212
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
32213
|
+
try {
|
|
32214
|
+
await move(tmp, file2);
|
|
32215
|
+
return;
|
|
32216
|
+
} catch (err) {
|
|
32217
|
+
const code = err.code;
|
|
32218
|
+
if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
|
|
32219
|
+
await delay(attempt * 10);
|
|
32220
|
+
}
|
|
32221
|
+
}
|
|
32222
|
+
}
|
|
32223
|
+
|
|
32224
|
+
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
32225
|
+
function createPolicyStore(dir = dataDir()) {
|
|
32226
|
+
const file2 = join19(dir, "policy-cache.json");
|
|
32227
|
+
async function read() {
|
|
32228
|
+
try {
|
|
32229
|
+
const raw = await readFile2(file2, "utf8");
|
|
32230
|
+
const parsed2 = JSON.parse(raw);
|
|
32231
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
32232
|
+
const record2 = parsed2;
|
|
32233
|
+
const bundle = PolicyBundle.parse(record2.bundle);
|
|
32234
|
+
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
32235
|
+
const etag = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
32236
|
+
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
32237
|
+
} catch {
|
|
32238
|
+
return null;
|
|
32239
|
+
}
|
|
32240
|
+
}
|
|
32241
|
+
async function write(bundle, etag) {
|
|
32242
|
+
await ensureDataDir(dir);
|
|
32243
|
+
const stored = {
|
|
32244
|
+
bundle,
|
|
32245
|
+
fetchedAtMs: Date.now(),
|
|
32246
|
+
...etag === void 0 ? {} : { etag }
|
|
32247
|
+
};
|
|
32248
|
+
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
32249
|
+
try {
|
|
32250
|
+
await writeFile2(tmp, JSON.stringify(stored), {
|
|
32251
|
+
encoding: "utf8",
|
|
32252
|
+
mode: DATA_FILE_MODE,
|
|
32253
|
+
flag: "wx"
|
|
32254
|
+
});
|
|
32255
|
+
await publishByRename(tmp, file2);
|
|
32256
|
+
} catch (err) {
|
|
32257
|
+
await rm(tmp, { force: true }).catch(() => void 0);
|
|
32258
|
+
throw err;
|
|
32259
|
+
}
|
|
32260
|
+
}
|
|
32261
|
+
return { read, write, file: file2 };
|
|
32262
|
+
}
|
|
32263
|
+
|
|
32264
|
+
// ../../packages/remote/src/http.ts
|
|
32265
|
+
import { request as httpRequest } from "http";
|
|
32266
|
+
import { request as httpsRequest } from "https";
|
|
32267
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
32268
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
32269
|
+
var RemoteRequestError = class extends Error {
|
|
32270
|
+
constructor(status) {
|
|
32271
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
32272
|
+
this.status = status;
|
|
32273
|
+
this.name = "RemoteRequestError";
|
|
32274
|
+
}
|
|
32275
|
+
status;
|
|
32276
|
+
};
|
|
32277
|
+
var RemoteRequestInvalid = class extends Error {
|
|
32278
|
+
constructor(route, cause) {
|
|
32279
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
32280
|
+
this.cause = cause;
|
|
32281
|
+
this.name = "RemoteRequestInvalid";
|
|
32282
|
+
}
|
|
32283
|
+
cause;
|
|
32284
|
+
};
|
|
32285
|
+
var RemoteResponseInvalid = class extends Error {
|
|
32286
|
+
constructor(route, detail) {
|
|
32287
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
32288
|
+
this.name = "RemoteResponseInvalid";
|
|
32289
|
+
}
|
|
32290
|
+
};
|
|
32291
|
+
var RemoteTransportError = class extends Error {
|
|
32292
|
+
/**
|
|
32293
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
32294
|
+
* refused.
|
|
32295
|
+
*
|
|
32296
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
32297
|
+
* all. It exists because two paths reject after a status has already been
|
|
32298
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
32299
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
32300
|
+
* outage, which sends the reader to look at their network instead of their
|
|
32301
|
+
* credential.
|
|
32302
|
+
*/
|
|
32303
|
+
constructor(reason, status) {
|
|
32304
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
32305
|
+
this.status = status;
|
|
32306
|
+
this.name = "RemoteTransportError";
|
|
32307
|
+
}
|
|
32308
|
+
status;
|
|
32309
|
+
};
|
|
32310
|
+
async function send(options) {
|
|
32311
|
+
const url2 = new URL(options.url);
|
|
32312
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32313
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
32314
|
+
const requestOptions = {
|
|
32315
|
+
method: options.method,
|
|
32316
|
+
headers: {
|
|
32317
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
32318
|
+
// last they win, and two of the values below are ones no caller may
|
|
32319
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
32320
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
32321
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
32322
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
32323
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
32324
|
+
// conditional GET — is untouched by this order.
|
|
32325
|
+
...options.headers,
|
|
32326
|
+
// The credential. One header, matching what the deployment authenticates
|
|
32327
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
32328
|
+
// it can be logged by an intermediary for no gain.
|
|
32329
|
+
"x-api-key": options.apiKey,
|
|
32330
|
+
accept: "application/json",
|
|
32331
|
+
...options.body === void 0 ? {} : {
|
|
32332
|
+
"content-type": "application/json",
|
|
32333
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
32334
|
+
// character count is truncated by the receiver.
|
|
32335
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
32336
|
+
}
|
|
32337
|
+
}
|
|
32338
|
+
};
|
|
32339
|
+
return new Promise((resolve2, reject) => {
|
|
32340
|
+
let settled = false;
|
|
32341
|
+
const fail = (reason, status) => {
|
|
32342
|
+
if (settled) return;
|
|
32343
|
+
settled = true;
|
|
32344
|
+
reject(new RemoteTransportError(reason, status));
|
|
32345
|
+
};
|
|
32346
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
32347
|
+
const chunks = [];
|
|
32348
|
+
let size = 0;
|
|
32349
|
+
res.on("data", (chunk) => {
|
|
32350
|
+
size += chunk.length;
|
|
32351
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
32352
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
32353
|
+
res.destroy();
|
|
32354
|
+
req.destroy();
|
|
32355
|
+
return;
|
|
32356
|
+
}
|
|
32357
|
+
chunks.push(chunk);
|
|
32358
|
+
});
|
|
32359
|
+
res.on("aborted", () => {
|
|
32360
|
+
fail("the response was aborted", res.statusCode);
|
|
32361
|
+
});
|
|
32362
|
+
res.on("end", () => {
|
|
32363
|
+
if (settled) return;
|
|
32364
|
+
settled = true;
|
|
32365
|
+
resolve2({
|
|
32366
|
+
status: res.statusCode ?? 0,
|
|
32367
|
+
headers: res.headers,
|
|
32368
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
32369
|
+
});
|
|
32370
|
+
});
|
|
32371
|
+
});
|
|
32372
|
+
const deadline = setTimeout(() => {
|
|
32373
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
32374
|
+
req.destroy();
|
|
32375
|
+
}, timeoutMs);
|
|
32376
|
+
deadline.unref();
|
|
32377
|
+
req.on("upgrade", (_res, socket) => {
|
|
32378
|
+
fail("the deployment answered with a protocol upgrade");
|
|
32379
|
+
socket.destroy();
|
|
32380
|
+
});
|
|
32381
|
+
req.on("close", () => {
|
|
32382
|
+
fail("the connection closed before a response was read");
|
|
32383
|
+
clearTimeout(deadline);
|
|
32384
|
+
});
|
|
32385
|
+
req.on("error", (err) => {
|
|
32386
|
+
fail(err.message);
|
|
32387
|
+
});
|
|
32388
|
+
if (options.body !== void 0) req.write(options.body);
|
|
32389
|
+
req.end();
|
|
32390
|
+
});
|
|
32391
|
+
}
|
|
32392
|
+
|
|
32393
|
+
// ../../packages/remote/src/client.ts
|
|
32394
|
+
var ROUTES = {
|
|
32395
|
+
events: "/v1/events",
|
|
32396
|
+
auditEvents: "/v1/audit-events",
|
|
32397
|
+
inventory: "/v1/inventory",
|
|
32398
|
+
storePosture: "/v1/store-posture",
|
|
32399
|
+
policyBundle: "/v1/policy-bundle",
|
|
32400
|
+
whoami: "/v1/plugin/whoami"
|
|
32401
|
+
};
|
|
32402
|
+
function headerValue(response, name) {
|
|
32403
|
+
const raw = response.headers[name];
|
|
32404
|
+
if (raw === void 0) return void 0;
|
|
32405
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
32406
|
+
}
|
|
32407
|
+
function okBody(response) {
|
|
32408
|
+
if (response.status < 200 || response.status >= 300) {
|
|
32409
|
+
throw new RemoteRequestError(response.status);
|
|
32410
|
+
}
|
|
32411
|
+
return response.body;
|
|
32412
|
+
}
|
|
32413
|
+
function parsed(schema, body, route) {
|
|
32414
|
+
let json2;
|
|
32415
|
+
try {
|
|
32416
|
+
json2 = JSON.parse(body);
|
|
32417
|
+
} catch {
|
|
32418
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
32419
|
+
}
|
|
32420
|
+
const result = schema.safeParse(json2);
|
|
32421
|
+
if (!result.success) {
|
|
32422
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
32423
|
+
}
|
|
32424
|
+
return result.data;
|
|
32425
|
+
}
|
|
32426
|
+
function withoutTrailingSlashes(endpoint) {
|
|
32427
|
+
let end = endpoint.length;
|
|
32428
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
32429
|
+
return endpoint.slice(0, end);
|
|
32430
|
+
}
|
|
32431
|
+
var SLASH = "/".charCodeAt(0);
|
|
32432
|
+
function createRemoteClient(options) {
|
|
32433
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
32434
|
+
const url2 = (route) => `${base}${route}`;
|
|
32435
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
32436
|
+
return {
|
|
32437
|
+
async ingestEvents(batch) {
|
|
32438
|
+
const response = await send({
|
|
32439
|
+
...common,
|
|
32440
|
+
method: "POST",
|
|
32441
|
+
url: url2(ROUTES.events),
|
|
32442
|
+
body: JSON.stringify(batch)
|
|
32443
|
+
});
|
|
32444
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
32445
|
+
},
|
|
32446
|
+
async ingestInventory(context) {
|
|
32447
|
+
const response = await send({
|
|
32448
|
+
...common,
|
|
32449
|
+
method: "POST",
|
|
32450
|
+
url: url2(ROUTES.inventory),
|
|
32451
|
+
body: JSON.stringify(context)
|
|
32452
|
+
});
|
|
32453
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
32454
|
+
},
|
|
32455
|
+
async recordAuditEvent(event) {
|
|
32456
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
32457
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
32458
|
+
const submission = validated.data;
|
|
32459
|
+
const response = await send({
|
|
32460
|
+
...common,
|
|
32461
|
+
method: "POST",
|
|
32462
|
+
url: url2(ROUTES.auditEvents),
|
|
32463
|
+
body: JSON.stringify(submission)
|
|
32464
|
+
});
|
|
32465
|
+
okBody(response);
|
|
32466
|
+
},
|
|
32467
|
+
async reportStorePosture(snapshot) {
|
|
32468
|
+
const response = await send({
|
|
32469
|
+
...common,
|
|
32470
|
+
method: "POST",
|
|
32471
|
+
url: url2(ROUTES.storePosture),
|
|
32472
|
+
body: JSON.stringify(snapshot)
|
|
32473
|
+
});
|
|
32474
|
+
okBody(response);
|
|
32475
|
+
},
|
|
32476
|
+
async getPolicyBundle(etag) {
|
|
32477
|
+
const response = await send({
|
|
32478
|
+
...common,
|
|
32479
|
+
method: "GET",
|
|
32480
|
+
url: url2(ROUTES.policyBundle),
|
|
32481
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
32482
|
+
});
|
|
32483
|
+
if (response.status === 304) {
|
|
32484
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
32485
|
+
}
|
|
32486
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
32487
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
32488
|
+
},
|
|
32489
|
+
async whoami() {
|
|
32490
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
32491
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
32492
|
+
}
|
|
32493
|
+
};
|
|
32494
|
+
}
|
|
32495
|
+
|
|
32496
|
+
// ../../packages/plugin-runtime/src/attached/posture-reporter.ts
|
|
32497
|
+
var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
32498
|
+
function createPostureReporter(deps) {
|
|
32499
|
+
async function prepare() {
|
|
32500
|
+
try {
|
|
32501
|
+
const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
|
|
32502
|
+
if (state === null) return null;
|
|
32503
|
+
const nowMs = deps.now();
|
|
32504
|
+
const elapsed = nowMs - state.lastAttemptedAtMs;
|
|
32505
|
+
if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
|
|
32506
|
+
try {
|
|
32507
|
+
await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
|
|
32508
|
+
} catch {
|
|
32509
|
+
}
|
|
32510
|
+
const { readError, ...measurement } = deps.readStore();
|
|
32511
|
+
if (readError) return null;
|
|
32512
|
+
let plugin;
|
|
32513
|
+
try {
|
|
32514
|
+
plugin = await deps.pluginBlock?.();
|
|
32515
|
+
} catch {
|
|
32516
|
+
plugin = void 0;
|
|
32517
|
+
}
|
|
32518
|
+
return {
|
|
32519
|
+
deviceId: state.deviceId,
|
|
32520
|
+
hostname: deps.hostname(),
|
|
32521
|
+
capturedAt: nowMs,
|
|
32522
|
+
...measurement,
|
|
32523
|
+
// Omit the key rather than spread an explicit `undefined` —
|
|
32524
|
+
// exactOptionalPropertyTypes distinguishes the two, and the bridge in
|
|
32525
|
+
// factory.ts keys on presence.
|
|
32526
|
+
...plugin === void 0 ? {} : { plugin }
|
|
32527
|
+
};
|
|
32528
|
+
} catch {
|
|
32529
|
+
return null;
|
|
32530
|
+
}
|
|
32531
|
+
}
|
|
32532
|
+
async function send2(snapshot) {
|
|
32533
|
+
try {
|
|
32534
|
+
await deps.report(snapshot);
|
|
32535
|
+
} catch {
|
|
32536
|
+
}
|
|
32537
|
+
}
|
|
32538
|
+
return { prepare, send: send2 };
|
|
32539
|
+
}
|
|
32540
|
+
|
|
32541
|
+
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
32542
|
+
import { statSync as statSync9 } from "fs";
|
|
32543
|
+
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
32544
|
+
|
|
32545
|
+
// ../../packages/plugin-runtime/src/attached/action-counts.ts
|
|
32546
|
+
function emptyActionCounts() {
|
|
32547
|
+
return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
|
|
32548
|
+
}
|
|
32549
|
+
function isActionTaken(value) {
|
|
32550
|
+
return ACTION_TAKEN_KEYS.includes(value);
|
|
32551
|
+
}
|
|
32552
|
+
|
|
32553
|
+
// ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
|
|
32554
|
+
var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
|
|
32555
|
+
function isSchemaAbsent(err) {
|
|
32556
|
+
return err instanceof Error && /no such table/i.test(err.message);
|
|
32557
|
+
}
|
|
32558
|
+
function emptyReadout(readError = false) {
|
|
32559
|
+
const byAction = emptyActionCounts();
|
|
32560
|
+
return {
|
|
32561
|
+
storePresent: false,
|
|
32562
|
+
schemaVersion: null,
|
|
32563
|
+
findingsTotal: 0,
|
|
32564
|
+
findingsFirstAt: null,
|
|
32565
|
+
findingsLastAt: null,
|
|
32566
|
+
packs: [],
|
|
32567
|
+
policyCounts: { total: 0, disabled: 0, byAction },
|
|
32568
|
+
readError
|
|
32569
|
+
};
|
|
32570
|
+
}
|
|
32571
|
+
function readStorePosture(dbPath2) {
|
|
32572
|
+
try {
|
|
32573
|
+
statSync9(dbPath2);
|
|
32574
|
+
} catch (err) {
|
|
32575
|
+
const code = err.code;
|
|
32576
|
+
if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
|
|
32577
|
+
return emptyReadout(true);
|
|
32578
|
+
}
|
|
32579
|
+
let db = null;
|
|
32580
|
+
let version2 = null;
|
|
32581
|
+
let packs2 = [];
|
|
32582
|
+
let policyCounts = {
|
|
32583
|
+
total: 0,
|
|
32584
|
+
disabled: 0,
|
|
32585
|
+
byAction: emptyActionCounts()
|
|
32586
|
+
};
|
|
32587
|
+
let findingsTotal = 0;
|
|
32588
|
+
let findingsFirstAt = null;
|
|
32589
|
+
let findingsLastAt = null;
|
|
32590
|
+
const currentReadout = () => ({
|
|
32591
|
+
storePresent: true,
|
|
32592
|
+
schemaVersion: version2,
|
|
32593
|
+
findingsTotal,
|
|
32594
|
+
findingsFirstAt,
|
|
32595
|
+
findingsLastAt,
|
|
32596
|
+
packs: packs2,
|
|
32597
|
+
policyCounts,
|
|
32598
|
+
readError: false
|
|
32599
|
+
});
|
|
32600
|
+
try {
|
|
32601
|
+
db = new DatabaseSync3(dbPath2, { readOnly: true });
|
|
32602
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
32603
|
+
version2 = db.prepare("PRAGMA user_version").get().user_version;
|
|
32604
|
+
try {
|
|
32605
|
+
const packRows = db.prepare(
|
|
32606
|
+
`SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
|
|
32607
|
+
).all();
|
|
32608
|
+
packs2 = packRows.map((r) => ({
|
|
32609
|
+
packId: `${r.namespace}/${r.pack_id}`,
|
|
32610
|
+
version: r.version,
|
|
32611
|
+
enabled: r.enabled !== 0,
|
|
32612
|
+
updatedAt: r.updated_at == null ? null : String(r.updated_at)
|
|
32613
|
+
}));
|
|
32614
|
+
} catch (err) {
|
|
32615
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32616
|
+
}
|
|
32617
|
+
try {
|
|
32618
|
+
const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
|
|
32619
|
+
const byAction = emptyActionCounts();
|
|
32620
|
+
let disabled = 0;
|
|
32621
|
+
for (const row of policyRows) {
|
|
32622
|
+
if (row.enabled === 0) disabled += 1;
|
|
32623
|
+
if (isActionTaken(row.action)) byAction[row.action] += 1;
|
|
32624
|
+
}
|
|
32625
|
+
policyCounts = { total: policyRows.length, disabled, byAction };
|
|
32626
|
+
} catch (err) {
|
|
32627
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32628
|
+
}
|
|
32629
|
+
try {
|
|
32630
|
+
const agg = db.prepare(
|
|
32631
|
+
`SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
|
|
32632
|
+
FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
|
|
32633
|
+
WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
|
|
32634
|
+
).get();
|
|
32635
|
+
findingsTotal = agg.n;
|
|
32636
|
+
findingsFirstAt = agg.firstAt;
|
|
32637
|
+
findingsLastAt = agg.lastAt;
|
|
32638
|
+
} catch (err) {
|
|
32639
|
+
if (!isSchemaAbsent(err)) throw err;
|
|
32640
|
+
}
|
|
32641
|
+
return currentReadout();
|
|
32642
|
+
} catch {
|
|
32643
|
+
return emptyReadout(true);
|
|
32644
|
+
} finally {
|
|
32645
|
+
try {
|
|
32646
|
+
db?.close();
|
|
32647
|
+
} catch {
|
|
32648
|
+
}
|
|
32649
|
+
}
|
|
32650
|
+
}
|
|
32651
|
+
|
|
32652
|
+
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
32653
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
32654
|
+
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
32655
|
+
import { join as join20 } from "path";
|
|
32656
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
32657
|
+
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
32658
|
+
const file2 = join20(dir, "posture-state.json");
|
|
32659
|
+
const legacyFile = legacyDir === void 0 ? null : join20(legacyDir, "posture-state.json");
|
|
32660
|
+
async function persist(state) {
|
|
32661
|
+
await ensureDataDir(dir);
|
|
32662
|
+
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
32663
|
+
try {
|
|
32664
|
+
await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
|
|
32665
|
+
await publishByRename(tmp, file2);
|
|
32666
|
+
} catch (err) {
|
|
32667
|
+
await rm2(tmp, { force: true }).catch(() => void 0);
|
|
32668
|
+
throw err;
|
|
32669
|
+
}
|
|
32670
|
+
}
|
|
32671
|
+
async function readFrom(path) {
|
|
32672
|
+
let raw;
|
|
32673
|
+
try {
|
|
32674
|
+
raw = await readFile3(path, "utf8");
|
|
32675
|
+
} catch (err) {
|
|
32676
|
+
const code = err.code;
|
|
32677
|
+
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
32678
|
+
throw err;
|
|
32679
|
+
}
|
|
32680
|
+
try {
|
|
32681
|
+
const parsed2 = JSON.parse(raw);
|
|
32682
|
+
if (typeof parsed2 === "object" && parsed2 !== null) {
|
|
32683
|
+
const record2 = parsed2;
|
|
32684
|
+
if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
|
|
32685
|
+
const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
|
|
32686
|
+
return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
|
|
32687
|
+
}
|
|
32688
|
+
}
|
|
32689
|
+
} catch {
|
|
32690
|
+
}
|
|
32691
|
+
return null;
|
|
32692
|
+
}
|
|
32693
|
+
async function read() {
|
|
32694
|
+
const current = await readFrom(file2);
|
|
32695
|
+
if (current) return current;
|
|
32696
|
+
const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
|
|
32697
|
+
if (legacy) {
|
|
32698
|
+
try {
|
|
32699
|
+
await persist(legacy);
|
|
32700
|
+
} catch {
|
|
32701
|
+
}
|
|
32702
|
+
return legacy;
|
|
32703
|
+
}
|
|
32704
|
+
const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
|
|
32705
|
+
try {
|
|
32706
|
+
await ensureDataDir(dir);
|
|
32707
|
+
if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
|
|
32708
|
+
} catch {
|
|
32709
|
+
return null;
|
|
32710
|
+
}
|
|
32711
|
+
const winner = await readFrom(file2).catch(() => null);
|
|
32712
|
+
if (winner) return winner;
|
|
32713
|
+
try {
|
|
32714
|
+
await persist(fresh);
|
|
32715
|
+
} catch {
|
|
32716
|
+
return null;
|
|
32717
|
+
}
|
|
32718
|
+
return fresh;
|
|
32719
|
+
}
|
|
32720
|
+
async function markAttempted(deviceId, atMs) {
|
|
32721
|
+
await persist({ deviceId, lastAttemptedAtMs: atMs });
|
|
32722
|
+
}
|
|
32723
|
+
return { read, markAttempted, file: file2 };
|
|
32724
|
+
}
|
|
32725
|
+
|
|
32726
|
+
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
32727
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
32728
|
+
import { join as join21 } from "path";
|
|
32729
|
+
|
|
32730
|
+
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
32731
|
+
var REFUSAL_LINES = {
|
|
32732
|
+
unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
|
|
32733
|
+
forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
|
|
32734
|
+
};
|
|
32735
|
+
var OUTCOME_LINES = {
|
|
32736
|
+
ok: "policy synced",
|
|
32737
|
+
"not-modified": "policy up to date",
|
|
32738
|
+
unauthorized: REFUSAL_LINES.unauthorized,
|
|
32739
|
+
forbidden: REFUSAL_LINES.forbidden,
|
|
32740
|
+
unreachable: "control plane unreachable at last attempt",
|
|
32741
|
+
"invalid-bundle": "control plane sent a policy bundle this build cannot read"
|
|
32742
|
+
};
|
|
32743
|
+
|
|
32744
|
+
// ../../packages/plugin-runtime/src/attached/sync-trigger.ts
|
|
32745
|
+
import { spawn } from "child_process";
|
|
32746
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
32747
|
+
var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
|
|
32748
|
+
|
|
32749
|
+
// ../../packages/plugin-runtime/src/attached/factory.ts
|
|
32750
|
+
import { hostname as hostname5 } from "os";
|
|
32751
|
+
|
|
32752
|
+
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
32753
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
32754
|
+
|
|
32755
|
+
// ../../packages/plugin-runtime/src/recorder.ts
|
|
32756
|
+
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
32757
|
+
|
|
32758
|
+
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
32759
|
+
var StandaloneDataGateway = class {
|
|
32760
|
+
db;
|
|
32761
|
+
// Kept for the fingerprint key lookup (exception.key lives beside the store).
|
|
32762
|
+
dataDir;
|
|
32763
|
+
// One notice per gateway — see warnRulesetDiscarded.
|
|
32764
|
+
warnedRulesetDiscarded = false;
|
|
32765
|
+
constructor(dataDir2, detections = [], meta3) {
|
|
32766
|
+
this.db = openLocalDatabase(dataDir2);
|
|
32767
|
+
this.dataDir = dataDir2;
|
|
32768
|
+
this.db.installedPacks.recordInventory(detections, meta3);
|
|
32769
|
+
}
|
|
32770
|
+
recordCapture(record2) {
|
|
32771
|
+
this.db.recordCapture(record2.event, record2.findings);
|
|
32772
|
+
return Promise.resolve();
|
|
32773
|
+
}
|
|
32774
|
+
ensureInventory(ctx) {
|
|
32775
|
+
return Promise.resolve(this.db.ensureInventory(ctx));
|
|
32776
|
+
}
|
|
32777
|
+
recordAuditEvent(event) {
|
|
32778
|
+
this.db.auditEvents.insertAuditEvent(event);
|
|
32779
|
+
return Promise.resolve();
|
|
32780
|
+
}
|
|
32781
|
+
// The id is minted inside the repository from the natural key — the plugin can't
|
|
32782
|
+
// import @akasecurity/persistence to compute it, so the gateway is the boundary that
|
|
32783
|
+
// hands the natural key across. UPSERT-take-MAX → idempotent re-reads that also
|
|
32784
|
+
// converge a streaming partial/final split (see insertLlmCall).
|
|
32785
|
+
recordLlmCall(input) {
|
|
32786
|
+
this.db.auditEvents.insertLlmCall(input);
|
|
32787
|
+
return Promise.resolve();
|
|
32788
|
+
}
|
|
32789
|
+
// One reconcile pass = one transaction. All leaves commit together
|
|
32790
|
+
// (single lock + WAL fsync); a contended SQLITE_BUSY rolls back and rejects so the
|
|
32791
|
+
// reconciler drops the whole pass and recovers it idempotently on the next read.
|
|
32792
|
+
recordLlmCalls(inputs) {
|
|
32793
|
+
if (inputs.length === 0) return Promise.resolve();
|
|
32794
|
+
return new Promise((resolve2, reject) => {
|
|
32795
|
+
try {
|
|
32796
|
+
this.db.auditEvents.runInTransaction(() => {
|
|
32797
|
+
for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
|
|
32798
|
+
});
|
|
32799
|
+
resolve2();
|
|
32800
|
+
} catch (err) {
|
|
32801
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
32802
|
+
}
|
|
32803
|
+
});
|
|
31342
32804
|
}
|
|
31343
32805
|
// One reconcile pass = one transaction (mirrors `recordLlmCalls`). Tool-call
|
|
31344
32806
|
// leaves are immutable facts (plain INSERT OR IGNORE), so a re-read no-ops on the
|
|
@@ -31346,12 +32808,12 @@ var StandaloneDataGateway = class {
|
|
|
31346
32808
|
// drops the whole pass and recovers it idempotently next time.
|
|
31347
32809
|
recordToolCalls(inputs) {
|
|
31348
32810
|
if (inputs.length === 0) return Promise.resolve();
|
|
31349
|
-
return new Promise((
|
|
32811
|
+
return new Promise((resolve2, reject) => {
|
|
31350
32812
|
try {
|
|
31351
32813
|
this.db.auditEvents.runInTransaction(() => {
|
|
31352
32814
|
for (const input of inputs) this.writeToolCall(input);
|
|
31353
32815
|
});
|
|
31354
|
-
|
|
32816
|
+
resolve2();
|
|
31355
32817
|
} catch (err) {
|
|
31356
32818
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
31357
32819
|
}
|
|
@@ -31493,7 +32955,7 @@ var StandaloneDataGateway = class {
|
|
|
31493
32955
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
31494
32956
|
const installed = this.installedScanRules();
|
|
31495
32957
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
31496
|
-
id:
|
|
32958
|
+
id: randomUUID18(),
|
|
31497
32959
|
scope: "global",
|
|
31498
32960
|
target: { ruleId },
|
|
31499
32961
|
action,
|
|
@@ -31647,19 +33109,66 @@ var StandaloneDataGateway = class {
|
|
|
31647
33109
|
}
|
|
31648
33110
|
};
|
|
31649
33111
|
|
|
33112
|
+
// ../../packages/plugin-runtime/src/attached/factory.ts
|
|
33113
|
+
function resolveGatewayForConfig(config2, meta3) {
|
|
33114
|
+
const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
|
|
33115
|
+
try {
|
|
33116
|
+
if (!isAttached(config2.settings)) return local;
|
|
33117
|
+
const connection = config2.settings.controlPlane;
|
|
33118
|
+
if (connection === void 0) return local;
|
|
33119
|
+
const state = readControlPlaneCredentialState(config2.settingsDir, connection);
|
|
33120
|
+
if (!state.usable) return local;
|
|
33121
|
+
const client = createRemoteClient({
|
|
33122
|
+
endpoint: connection.endpoint,
|
|
33123
|
+
apiKey: state.credential.apiKey
|
|
33124
|
+
});
|
|
33125
|
+
const store = createPolicyStore(config2.dataDir);
|
|
33126
|
+
const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
|
|
33127
|
+
const forward = createForwardPolicy({ dir: config2.dataDir });
|
|
33128
|
+
return new AttachedDataGateway({
|
|
33129
|
+
local,
|
|
33130
|
+
client,
|
|
33131
|
+
dataDir: config2.dataDir,
|
|
33132
|
+
readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
|
|
33133
|
+
forward,
|
|
33134
|
+
posture: createPostureReporter({
|
|
33135
|
+
// THROUGH THE BREAKER, and wrapped HERE rather than around
|
|
33136
|
+
// `PostureReporter.send`. The reporter swallows every error by
|
|
33137
|
+
// contract, so a wrap outside it would hand `forward.run` a resolved
|
|
33138
|
+
// promise for a send that failed — recording a SUCCESS, clearing
|
|
33139
|
+
// `consecutiveFailures` and `lastFailure`, and telling `aka status` the
|
|
33140
|
+
// forward recovered when nothing did. Wrapping the raw client call puts
|
|
33141
|
+
// the breaker above the swallow, where it can see the truth.
|
|
33142
|
+
//
|
|
33143
|
+
// What it buys: once the breaker is open — the plane already confirmed
|
|
33144
|
+
// down by the gateway's own writes — this stops paying a request
|
|
33145
|
+
// timeout per throttle interval to re-learn it.
|
|
33146
|
+
report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
|
|
33147
|
+
store: postureStore,
|
|
33148
|
+
readStore: () => readStorePosture(config2.dbPath),
|
|
33149
|
+
hostname: () => hostname5(),
|
|
33150
|
+
now: () => Date.now()
|
|
33151
|
+
})
|
|
33152
|
+
});
|
|
33153
|
+
} catch {
|
|
33154
|
+
return local;
|
|
33155
|
+
}
|
|
33156
|
+
}
|
|
33157
|
+
|
|
31650
33158
|
// ../../packages/plugin-runtime/src/resolve.ts
|
|
31651
|
-
var
|
|
31652
|
-
var defaultGatewayFactory =
|
|
33159
|
+
var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
|
|
33160
|
+
var defaultGatewayFactory = configuredGatewayFactory;
|
|
31653
33161
|
function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
|
|
31654
33162
|
return gatewayFactory(config2, meta3);
|
|
31655
33163
|
}
|
|
31656
33164
|
|
|
31657
33165
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
31658
|
-
import { randomUUID as
|
|
33166
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
31659
33167
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
31660
33168
|
|
|
31661
33169
|
// src/hooks/store-health.ts
|
|
31662
33170
|
var STORE_WARNING_MARKER = "store-warning-last-session";
|
|
33171
|
+
var STORE_REDIRECT_MARKER = "store-redirect-last-session";
|
|
31663
33172
|
function openGatewayOrNull(config2) {
|
|
31664
33173
|
try {
|
|
31665
33174
|
return resolveDataGateway(config2);
|
|
@@ -31672,17 +33181,64 @@ function storeUnavailableMessage(dbPath2) {
|
|
|
31672
33181
|
}
|
|
31673
33182
|
function claimStoreUnavailableWarning(dataDir2, sessionId) {
|
|
31674
33183
|
if (!sessionId) return true;
|
|
31675
|
-
const
|
|
31676
|
-
|
|
31677
|
-
|
|
31678
|
-
|
|
33184
|
+
const dirs = markerDirs(dataDir2);
|
|
33185
|
+
if (alreadyClaimed(dirs, STORE_WARNING_MARKER, sessionId)) return false;
|
|
33186
|
+
recordClaim(dirs, STORE_WARNING_MARKER, sessionId);
|
|
33187
|
+
return true;
|
|
33188
|
+
}
|
|
33189
|
+
function markerDirs(dataDir2) {
|
|
33190
|
+
return [dataDir2, dirname5(dataDir2)];
|
|
33191
|
+
}
|
|
33192
|
+
function alreadyClaimed(dirs, marker, sessionId) {
|
|
33193
|
+
return dirs.some((dir) => {
|
|
33194
|
+
try {
|
|
33195
|
+
return readFileSync14(join22(dir, marker), "utf8") === sessionId;
|
|
33196
|
+
} catch {
|
|
33197
|
+
return false;
|
|
33198
|
+
}
|
|
33199
|
+
});
|
|
33200
|
+
}
|
|
33201
|
+
function recordClaim(dirs, marker, sessionId) {
|
|
33202
|
+
for (const dir of dirs) {
|
|
33203
|
+
try {
|
|
33204
|
+
mkdirSync4(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
33205
|
+
writeFileSync7(join22(dir, marker), sessionId, { mode: DATA_FILE_MODE });
|
|
33206
|
+
return;
|
|
33207
|
+
} catch {
|
|
33208
|
+
}
|
|
31679
33209
|
}
|
|
33210
|
+
}
|
|
33211
|
+
function storeRedirectedMessage(paths, platform2 = process.platform) {
|
|
33212
|
+
const where = paths.map(({ path, target, holds, missing, mode }) => {
|
|
33213
|
+
if (missing) return `${path} -> ${target} (which does not exist; ${holds} cannot land there)`;
|
|
33214
|
+
const loose = mode !== void 0 && (mode & 63) !== 0 ? ", NOT owner-only" : "";
|
|
33215
|
+
const inherited = mode === void 0 ? "" : ` (${formatMode(mode)}${loose})`;
|
|
33216
|
+
return `${path} -> ${target}${inherited}, holding ${holds}`;
|
|
33217
|
+
}).join("; ");
|
|
33218
|
+
const subject = paths.length === 1 ? "a store path is a symlink" : `${String(paths.length)} store paths are symlinks`;
|
|
33219
|
+
const anyResolves = paths.some(({ missing }) => !missing);
|
|
33220
|
+
const lead = anyResolves ? `${subject}, so AKA is writing into the target instead: ${where}. ` : `${subject} resolving nowhere, so AKA cannot write there: ${where}. `;
|
|
33221
|
+
const kept = anyResolves && platform2 !== "win32" ? "Permissions are never changed through a symlink, so the store keeps whatever the target already had. " : "";
|
|
33222
|
+
return `[aka] ${lead}${kept}If you did not create that link, treat it as untrusted and run \`aka init\` for the full report.
|
|
33223
|
+
`;
|
|
33224
|
+
}
|
|
33225
|
+
function formatMode(mode) {
|
|
33226
|
+
return `0${mode.toString(8).padStart(3, "0")}`;
|
|
33227
|
+
}
|
|
33228
|
+
function warnIfStoreRedirected(config2, sessionId, write = (message) => void process.stderr.write(message)) {
|
|
31680
33229
|
try {
|
|
31681
|
-
|
|
31682
|
-
|
|
33230
|
+
const paths = symlinkedStorePaths(dirname5(config2.dataDir));
|
|
33231
|
+
if (paths.length === 0) return;
|
|
33232
|
+
if (!sessionId) {
|
|
33233
|
+
write(storeRedirectedMessage(paths));
|
|
33234
|
+
return;
|
|
33235
|
+
}
|
|
33236
|
+
const dirs = markerDirs(config2.dataDir);
|
|
33237
|
+
if (alreadyClaimed(dirs, STORE_REDIRECT_MARKER, sessionId)) return;
|
|
33238
|
+
write(storeRedirectedMessage(paths));
|
|
33239
|
+
recordClaim(dirs, STORE_REDIRECT_MARKER, sessionId);
|
|
31683
33240
|
} catch {
|
|
31684
33241
|
}
|
|
31685
|
-
return true;
|
|
31686
33242
|
}
|
|
31687
33243
|
|
|
31688
33244
|
// src/present.ts
|
|
@@ -31797,6 +33353,7 @@ async function main() {
|
|
|
31797
33353
|
if (prompt === void 0 || prompt === "") return;
|
|
31798
33354
|
const config2 = loadConfig();
|
|
31799
33355
|
const sessionId = input ? getString(input, "session_id") : void 0;
|
|
33356
|
+
warnIfStoreRedirected(config2, sessionId);
|
|
31800
33357
|
const metadata = input ? baseMetadata(input) : void 0;
|
|
31801
33358
|
const gateway = openGatewayOrNull(config2);
|
|
31802
33359
|
if (gateway === null) {
|