@guilz-dev/belay 0.9.4 → 0.10.1
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/README.md +3 -2
- package/dist/adapters/claude/runtime-entry.js +5 -18
- package/dist/adapters/codex/runtime-entry.js +10 -11
- package/dist/adapters/cursor/hook-dispatch-entry.d.ts +3 -0
- package/dist/adapters/cursor/hook-dispatch-entry.js +11 -0
- package/dist/adapters/cursor/hook-router.d.ts +1 -0
- package/dist/adapters/cursor/hook-router.js +39 -9
- package/dist/adapters/cursor/hook-routing-health.d.ts +1 -0
- package/dist/adapters/cursor/hook-routing-health.js +53 -0
- package/dist/adapters/cursor/hooks.d.ts +1 -1
- package/dist/adapters/cursor/hooks.js +16 -5
- package/dist/adapters/cursor/routing-config-trust.d.ts +1 -0
- package/dist/adapters/cursor/routing-config-trust.js +67 -0
- package/dist/adapters/cursor/runtime-entry.d.ts +2 -2
- package/dist/adapters/cursor/runtime-entry.js +35 -10
- package/dist/adapters/shared/gate-runtime.d.ts +0 -2
- package/dist/adapters/shared/gate-runtime.js +12 -52
- package/dist/bundle/claude-runtime.mjs +1083 -669
- package/dist/bundle/codex-runtime.mjs +1092 -698
- package/dist/bundle/cursor-dispatcher.mjs +119 -28
- package/dist/bundle/cursor-runtime.mjs +1132 -677
- package/dist/cli.js +53 -4
- package/dist/commands/approval-token.d.ts +10 -0
- package/dist/commands/approval-token.js +26 -0
- package/dist/commands/audit.d.ts +2 -1
- package/dist/commands/audit.js +2 -2
- package/dist/commands/config.d.ts +1 -1
- package/dist/commands/config.js +28 -2
- package/dist/commands/doctor.js +14 -54
- package/dist/commands/dogfood-check.d.ts +3 -0
- package/dist/commands/dogfood-check.js +116 -0
- package/dist/commands/dogfood.d.ts +1 -0
- package/dist/commands/dogfood.js +4 -3
- package/dist/commands/judge.js +2 -2
- package/dist/commands/recovery-checkpoints.js +2 -11
- package/dist/config-io.d.ts +1 -0
- package/dist/config-io.js +8 -1
- package/dist/core/capability/index.d.ts +1 -1
- package/dist/core/capability/index.js +1 -1
- package/dist/core/capability/policy-engine.d.ts +19 -0
- package/dist/core/capability/policy-engine.js +31 -0
- package/dist/core/classify-tool.js +372 -148
- package/dist/core/config.js +2 -2
- package/dist/core/dogfood-environment.d.ts +8 -0
- package/dist/core/dogfood-environment.js +55 -0
- package/dist/core/effect-ir/shell-lower/decoders/belay.js +12 -0
- package/dist/core/egress-approval.js +0 -18
- package/dist/core/gate-engine.js +2 -9
- package/dist/core/notify.d.ts +8 -2
- package/dist/core/notify.js +63 -7
- package/dist/core/recovery/operator-guidance.js +4 -4
- package/dist/core/repo-config-trust.d.ts +31 -0
- package/dist/core/repo-config-trust.js +152 -0
- package/dist/corpus/benign-probe-cores.d.ts +1 -1
- package/dist/corpus/benign-probe-cores.js +0 -1
- package/dist/defaults.js +0 -25
- package/dist/installer/scope-config.js +2 -2
- package/dist/installer.js +4 -4
- package/dist/types.d.ts +19 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -1
|
@@ -2155,7 +2155,7 @@ function normalizeConfig(config) {
|
|
|
2155
2155
|
policy: {
|
|
2156
2156
|
unknownLocalEffect: v4.policy?.unknownLocalEffect === "deny" ? "deny" : v4.policy?.unknownLocalEffect === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unknownLocalEffect,
|
|
2157
2157
|
unparseableShell: v4.policy?.unparseableShell === "deny" ? "deny" : v4.policy?.unparseableShell === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unparseableShell,
|
|
2158
|
-
codexUnmappedTool:
|
|
2158
|
+
codexUnmappedTool: "allow",
|
|
2159
2159
|
fenceWarnThreshold: typeof v4.policy?.fenceWarnThreshold === "number" && v4.policy.fenceWarnThreshold > 0 && v4.policy.fenceWarnThreshold <= 1 ? v4.policy.fenceWarnThreshold : DEFAULT_FENCE_WARN_THRESHOLD,
|
|
2160
2160
|
confidenceThresholds: {
|
|
2161
2161
|
allow: typeof v4.policy?.confidenceThresholds?.allow === "number" ? v4.policy.confidenceThresholds.allow : DEFAULT_CONFIDENCE_THRESHOLDS.allow,
|
|
@@ -2457,7 +2457,7 @@ var init_config = __esm({
|
|
|
2457
2457
|
DEFAULT_POLICY_V3 = {
|
|
2458
2458
|
unknownLocalEffect: "allow_flagged",
|
|
2459
2459
|
unparseableShell: "deny",
|
|
2460
|
-
codexUnmappedTool: "
|
|
2460
|
+
codexUnmappedTool: "allow",
|
|
2461
2461
|
confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
|
|
2462
2462
|
modelAssist: { ...DEFAULT_MODEL_ASSIST },
|
|
2463
2463
|
transactional: { ...DEFAULT_TRANSACTIONAL_V3 },
|
|
@@ -3510,9 +3510,122 @@ var init_config_layers = __esm({
|
|
|
3510
3510
|
}
|
|
3511
3511
|
});
|
|
3512
3512
|
|
|
3513
|
-
// src/config-
|
|
3513
|
+
// src/core/repo-config-trust.ts
|
|
3514
3514
|
import { existsSync as existsSync3 } from "node:fs";
|
|
3515
|
-
import { chmod, mkdir as mkdir2, open as open2, readFile as readFile2, rename, unlink as unlink2
|
|
3515
|
+
import { chmod, mkdir as mkdir2, open as open2, readFile as readFile2, rename, unlink as unlink2 } from "node:fs/promises";
|
|
3516
|
+
import path12 from "node:path";
|
|
3517
|
+
function isObjectRecord(value) {
|
|
3518
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3519
|
+
}
|
|
3520
|
+
function hasOnlyExpectedKeys(value) {
|
|
3521
|
+
const expected = /* @__PURE__ */ new Set([
|
|
3522
|
+
"schemaVersion",
|
|
3523
|
+
"repoRoot",
|
|
3524
|
+
"adapter",
|
|
3525
|
+
"repoConfigFingerprint",
|
|
3526
|
+
"trustedAt"
|
|
3527
|
+
]);
|
|
3528
|
+
const keys = Object.keys(value);
|
|
3529
|
+
if (keys.length !== expected.size) {
|
|
3530
|
+
return false;
|
|
3531
|
+
}
|
|
3532
|
+
return keys.every((key) => expected.has(key));
|
|
3533
|
+
}
|
|
3534
|
+
function isAdapterName(value) {
|
|
3535
|
+
return value === "cursor" || value === "claude" || value === "codex";
|
|
3536
|
+
}
|
|
3537
|
+
function isIsoTimestamp(value) {
|
|
3538
|
+
return Number.isFinite(Date.parse(value));
|
|
3539
|
+
}
|
|
3540
|
+
function parseStrictTrustRecord(value) {
|
|
3541
|
+
if (!isObjectRecord(value) || !hasOnlyExpectedKeys(value)) {
|
|
3542
|
+
return null;
|
|
3543
|
+
}
|
|
3544
|
+
if (value.schemaVersion !== 1) {
|
|
3545
|
+
return null;
|
|
3546
|
+
}
|
|
3547
|
+
if (typeof value.repoRoot !== "string" || value.repoRoot.trim().length === 0) {
|
|
3548
|
+
return null;
|
|
3549
|
+
}
|
|
3550
|
+
if (!isAdapterName(value.adapter)) {
|
|
3551
|
+
return null;
|
|
3552
|
+
}
|
|
3553
|
+
if (typeof value.repoConfigFingerprint !== "string" || !/^[a-f0-9]{64}$/.test(value.repoConfigFingerprint)) {
|
|
3554
|
+
return null;
|
|
3555
|
+
}
|
|
3556
|
+
if (typeof value.trustedAt !== "string" || !isIsoTimestamp(value.trustedAt)) {
|
|
3557
|
+
return null;
|
|
3558
|
+
}
|
|
3559
|
+
return {
|
|
3560
|
+
schemaVersion: 1,
|
|
3561
|
+
repoRoot: value.repoRoot,
|
|
3562
|
+
adapter: value.adapter,
|
|
3563
|
+
repoConfigFingerprint: value.repoConfigFingerprint,
|
|
3564
|
+
trustedAt: value.trustedAt
|
|
3565
|
+
};
|
|
3566
|
+
}
|
|
3567
|
+
function repoConfigFingerprint(rawConfig) {
|
|
3568
|
+
return hashValue(canonicalStringify(rawConfig));
|
|
3569
|
+
}
|
|
3570
|
+
function repoConfigTrustPath(repoRoot, adapter) {
|
|
3571
|
+
const canonicalRepoRoot = canonicalPath(repoRoot);
|
|
3572
|
+
const identity = hashValue(`${canonicalRepoRoot}\0${adapter}`);
|
|
3573
|
+
return path12.join(defaultControlPlaneDir(), "config-trust", `${identity}.json`);
|
|
3574
|
+
}
|
|
3575
|
+
async function inspectRepoConfigTrust(repoRoot, adapter, rawConfig) {
|
|
3576
|
+
const canonicalRepoRoot = canonicalPath(repoRoot);
|
|
3577
|
+
const recordPath = repoConfigTrustPath(repoRoot, adapter);
|
|
3578
|
+
if (!existsSync3(recordPath)) {
|
|
3579
|
+
return { trusted: false, recordPath, reason: "missing" };
|
|
3580
|
+
}
|
|
3581
|
+
let parsed;
|
|
3582
|
+
try {
|
|
3583
|
+
parsed = JSON.parse(await readFile2(recordPath, "utf8"));
|
|
3584
|
+
} catch {
|
|
3585
|
+
return { trusted: false, recordPath, reason: "malformed" };
|
|
3586
|
+
}
|
|
3587
|
+
const record = parseStrictTrustRecord(parsed);
|
|
3588
|
+
if (!record) {
|
|
3589
|
+
return { trusted: false, recordPath, reason: "malformed" };
|
|
3590
|
+
}
|
|
3591
|
+
if (record.adapter !== adapter || canonicalPath(record.repoRoot) !== canonicalRepoRoot) {
|
|
3592
|
+
return { trusted: false, recordPath, reason: "identity_mismatch" };
|
|
3593
|
+
}
|
|
3594
|
+
const fingerprint = repoConfigFingerprint(rawConfig);
|
|
3595
|
+
if (record.repoConfigFingerprint !== fingerprint) {
|
|
3596
|
+
return { trusted: false, recordPath, reason: "fingerprint_mismatch" };
|
|
3597
|
+
}
|
|
3598
|
+
return { trusted: true, recordPath, fingerprint };
|
|
3599
|
+
}
|
|
3600
|
+
async function assertRepoConfigTrusted(repoRoot, adapter, rawConfig) {
|
|
3601
|
+
const status = await inspectRepoConfigTrust(repoRoot, adapter, rawConfig);
|
|
3602
|
+
if (status.trusted) {
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
throw new RepoConfigTrustError(status);
|
|
3606
|
+
}
|
|
3607
|
+
var TRUST_MESSAGE, RepoConfigTrustError;
|
|
3608
|
+
var init_repo_config_trust = __esm({
|
|
3609
|
+
"src/core/repo-config-trust.ts"() {
|
|
3610
|
+
"use strict";
|
|
3611
|
+
init_config();
|
|
3612
|
+
init_fingerprint2();
|
|
3613
|
+
init_path_utils();
|
|
3614
|
+
TRUST_MESSAGE = "Repository config is not trusted. Review it, then run `belay config trust`.";
|
|
3615
|
+
RepoConfigTrustError = class extends Error {
|
|
3616
|
+
status;
|
|
3617
|
+
constructor(status) {
|
|
3618
|
+
super(TRUST_MESSAGE);
|
|
3619
|
+
this.name = "RepoConfigTrustError";
|
|
3620
|
+
this.status = status;
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
});
|
|
3625
|
+
|
|
3626
|
+
// src/config-io.ts
|
|
3627
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3628
|
+
import { chmod as chmod2, mkdir as mkdir3, open as open3, readFile as readFile3, rename as rename2, unlink as unlink3, writeFile } from "node:fs/promises";
|
|
3516
3629
|
function resolveAdapterName(config) {
|
|
3517
3630
|
if (config.adapter === "claude") {
|
|
3518
3631
|
return "claude";
|
|
@@ -3523,10 +3636,10 @@ function resolveAdapterName(config) {
|
|
|
3523
3636
|
return "cursor";
|
|
3524
3637
|
}
|
|
3525
3638
|
function detectAdapterName(repoRoot) {
|
|
3526
|
-
if (
|
|
3639
|
+
if (existsSync4(configPathFor(repoRoot, "claude"))) {
|
|
3527
3640
|
return "claude";
|
|
3528
3641
|
}
|
|
3529
|
-
if (
|
|
3642
|
+
if (existsSync4(configPathFor(repoRoot, "codex"))) {
|
|
3530
3643
|
return "codex";
|
|
3531
3644
|
}
|
|
3532
3645
|
return "cursor";
|
|
@@ -3541,20 +3654,20 @@ async function loadLayeredConfig(repoRoot, adapter = detectAdapterName(repoRoot)
|
|
|
3541
3654
|
const layout = getAdapterLayout(adapter);
|
|
3542
3655
|
const configPath = configPathFor(repoRoot, adapter);
|
|
3543
3656
|
let repoConfig = {};
|
|
3544
|
-
if (
|
|
3545
|
-
repoConfig = JSON.parse(await
|
|
3657
|
+
if (existsSync4(configPath)) {
|
|
3658
|
+
repoConfig = JSON.parse(await readFile3(configPath, "utf8"));
|
|
3546
3659
|
}
|
|
3547
3660
|
let teamConfig = null;
|
|
3548
3661
|
const teamPath = teamConfigPath();
|
|
3549
|
-
if (
|
|
3550
|
-
teamConfig = JSON.parse(await
|
|
3662
|
+
if (existsSync4(teamPath)) {
|
|
3663
|
+
teamConfig = JSON.parse(await readFile3(teamPath, "utf8"));
|
|
3551
3664
|
}
|
|
3552
3665
|
return resolveLayeredConfig({
|
|
3553
3666
|
repoConfig,
|
|
3554
3667
|
adapterDefaults: layout.defaultConfig(repoRoot),
|
|
3555
3668
|
teamConfig,
|
|
3556
3669
|
teamConfigPath: teamPath,
|
|
3557
|
-
repoConfigPath:
|
|
3670
|
+
repoConfigPath: existsSync4(configPath) ? configPath : void 0
|
|
3558
3671
|
});
|
|
3559
3672
|
}
|
|
3560
3673
|
async function loadConfigFile(repoRoot, adapter = detectAdapterName(repoRoot)) {
|
|
@@ -3569,6 +3682,7 @@ var init_config_io = __esm({
|
|
|
3569
3682
|
init_approval_state_mutation();
|
|
3570
3683
|
init_config();
|
|
3571
3684
|
init_config_layers();
|
|
3685
|
+
init_repo_config_trust();
|
|
3572
3686
|
}
|
|
3573
3687
|
});
|
|
3574
3688
|
|
|
@@ -4230,9 +4344,9 @@ init_claude();
|
|
|
4230
4344
|
init_approval();
|
|
4231
4345
|
init_approval_replay();
|
|
4232
4346
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
4233
|
-
import { existsSync as
|
|
4234
|
-
import { mkdir as
|
|
4235
|
-
import
|
|
4347
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
4348
|
+
import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile11 } from "node:fs/promises";
|
|
4349
|
+
import path63 from "node:path";
|
|
4236
4350
|
|
|
4237
4351
|
// src/core/approval-service.ts
|
|
4238
4352
|
init_config_io();
|
|
@@ -4242,47 +4356,35 @@ init_approval_replay();
|
|
|
4242
4356
|
// src/core/approval-token.ts
|
|
4243
4357
|
init_config();
|
|
4244
4358
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
4245
|
-
import { existsSync as
|
|
4246
|
-
import { mkdir as
|
|
4247
|
-
import
|
|
4248
|
-
function base64UrlEncode(value) {
|
|
4249
|
-
return Buffer.from(value, "utf8").toString("base64url");
|
|
4250
|
-
}
|
|
4359
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
4360
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
4361
|
+
import path13 from "node:path";
|
|
4251
4362
|
function base64UrlDecode(value) {
|
|
4252
4363
|
return Buffer.from(value, "base64url").toString("utf8");
|
|
4253
4364
|
}
|
|
4254
4365
|
function approvalSigningKeyPath(controlPlaneDir = defaultControlPlaneDir()) {
|
|
4255
|
-
return
|
|
4366
|
+
return path13.join(controlPlaneDir, "approval-signing.key");
|
|
4256
4367
|
}
|
|
4257
4368
|
async function loadOrCreateApprovalSigningKey(controlPlaneDir = defaultControlPlaneDir()) {
|
|
4258
4369
|
const keyPath = approvalSigningKeyPath(controlPlaneDir);
|
|
4259
|
-
if (
|
|
4260
|
-
return
|
|
4370
|
+
if (existsSync5(keyPath)) {
|
|
4371
|
+
return readFile4(keyPath);
|
|
4261
4372
|
}
|
|
4262
|
-
await
|
|
4373
|
+
await mkdir4(controlPlaneDir, { recursive: true });
|
|
4263
4374
|
const key = randomBytes(32);
|
|
4264
4375
|
await writeFile2(keyPath, key, { mode: 384 });
|
|
4265
4376
|
return key;
|
|
4266
4377
|
}
|
|
4267
|
-
function signPayload(payload, key) {
|
|
4268
|
-
const body = base64UrlEncode(JSON.stringify(payload));
|
|
4269
|
-
const signature = createHmac("sha256", key).update(body).digest("base64url");
|
|
4270
|
-
return `${body}.${signature}`;
|
|
4271
|
-
}
|
|
4272
|
-
async function issueApprovalToken(payload, controlPlaneDir = defaultControlPlaneDir()) {
|
|
4273
|
-
const key = await loadOrCreateApprovalSigningKey(controlPlaneDir);
|
|
4274
|
-
return signPayload(payload, key);
|
|
4275
|
-
}
|
|
4276
4378
|
async function verifyApprovalToken(token, controlPlaneDir = defaultControlPlaneDir()) {
|
|
4277
4379
|
const [body, signature] = token.split(".");
|
|
4278
4380
|
if (!body || !signature) {
|
|
4279
4381
|
return null;
|
|
4280
4382
|
}
|
|
4281
4383
|
const keyPath = approvalSigningKeyPath(controlPlaneDir);
|
|
4282
|
-
if (!
|
|
4384
|
+
if (!existsSync5(keyPath)) {
|
|
4283
4385
|
return null;
|
|
4284
4386
|
}
|
|
4285
|
-
const key = await
|
|
4387
|
+
const key = await readFile4(keyPath);
|
|
4286
4388
|
const expected = createHmac("sha256", key).update(body).digest("base64url");
|
|
4287
4389
|
const actualBuffer = Buffer.from(signature);
|
|
4288
4390
|
const expectedBuffer = Buffer.from(expected);
|
|
@@ -4489,11 +4591,11 @@ init_approval_v3();
|
|
|
4489
4591
|
|
|
4490
4592
|
// src/core/capability/boundary-attestation-sign.ts
|
|
4491
4593
|
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
4492
|
-
import { access, readFile as
|
|
4594
|
+
import { access, readFile as readFile5 } from "node:fs/promises";
|
|
4493
4595
|
init_fingerprint2();
|
|
4494
4596
|
|
|
4495
4597
|
// src/core/capability/attestation.ts
|
|
4496
|
-
import
|
|
4598
|
+
import path14 from "node:path";
|
|
4497
4599
|
var BOUNDARY_ATTESTATION_VERSION = 1;
|
|
4498
4600
|
var CONTAINED_EXECUTION_ATTESTATION_VERSION = 1;
|
|
4499
4601
|
var KNOWN_DRIVERS = /* @__PURE__ */ new Set([
|
|
@@ -4561,7 +4663,7 @@ function validateContainedExecutionAttestation(value) {
|
|
|
4561
4663
|
const record = value;
|
|
4562
4664
|
const dockerSubstrate = record.dockerSubstrate;
|
|
4563
4665
|
const dockerConfiguration = record.dockerConfiguration;
|
|
4564
|
-
if (record.version !== CONTAINED_EXECUTION_ATTESTATION_VERSION || typeof record.imageId !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(record.imageId) || typeof record.imageReference !== "string" || !record.imageReference || /[\0\n\r]/.test(record.imageReference) || record.networkNone !== true || record.isolatesWorkspaceMirror !== true || record.readOnlyRoot !== true || record.sanitizedEnvironment !== true || !isRecord(dockerSubstrate) || typeof dockerSubstrate.binaryPath !== "string" || !
|
|
4666
|
+
if (record.version !== CONTAINED_EXECUTION_ATTESTATION_VERSION || typeof record.imageId !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(record.imageId) || typeof record.imageReference !== "string" || !record.imageReference || /[\0\n\r]/.test(record.imageReference) || record.networkNone !== true || record.isolatesWorkspaceMirror !== true || record.readOnlyRoot !== true || record.sanitizedEnvironment !== true || !isRecord(dockerSubstrate) || typeof dockerSubstrate.binaryPath !== "string" || !path14.isAbsolute(dockerSubstrate.binaryPath) || /[\0\n\r]/.test(dockerSubstrate.binaryPath) || typeof dockerSubstrate.binarySha256 !== "string" || !/^[a-f0-9]{64}$/.test(dockerSubstrate.binarySha256) || typeof dockerSubstrate.endpoint !== "string" || !dockerSubstrate.endpoint.startsWith("unix:///") || !path14.isAbsolute(dockerSubstrate.endpoint.slice("unix://".length)) || /[\0\n\r]/.test(dockerSubstrate.endpoint) || typeof dockerSubstrate.daemonId !== "string" || !dockerSubstrate.daemonId || /[\0\n\r]/.test(dockerSubstrate.daemonId) || !isRecord(dockerConfiguration) || typeof dockerConfiguration.executable !== "string" || !path14.isAbsolute(dockerConfiguration.executable) || /[\0\n\r]/.test(dockerConfiguration.executable) || typeof dockerConfiguration.host !== "string" || !dockerConfiguration.host.startsWith("unix:///") || !path14.isAbsolute(dockerConfiguration.host.slice("unix://".length)) || /[\0\n\r]/.test(dockerConfiguration.host) || typeof record.user !== "string" || !/^\d+:\d+$/.test(record.user) || record.entrypoint !== "/bin/sh" || record.capDropAll !== true || record.noNewPrivileges !== true || record.logDriver !== "none" || record.proxyEnvironment !== "neutralized-empty" || typeof record.probedAt !== "string" || typeof record.expiresAt !== "string") {
|
|
4565
4667
|
return false;
|
|
4566
4668
|
}
|
|
4567
4669
|
const probedAt = Date.parse(record.probedAt);
|
|
@@ -4631,7 +4733,7 @@ async function verifySignedBoundaryAttestation(params) {
|
|
|
4631
4733
|
return record.attestation;
|
|
4632
4734
|
}
|
|
4633
4735
|
async function readSignedAttestationFile(filePath) {
|
|
4634
|
-
return JSON.parse(await
|
|
4736
|
+
return JSON.parse(await readFile5(filePath, "utf8"));
|
|
4635
4737
|
}
|
|
4636
4738
|
|
|
4637
4739
|
// src/core/capability/boundary-egress.ts
|
|
@@ -4800,13 +4902,13 @@ async function runWithBoundaryRunnable(target, params) {
|
|
|
4800
4902
|
}
|
|
4801
4903
|
|
|
4802
4904
|
// src/core/capability/boundary-session.ts
|
|
4803
|
-
import
|
|
4905
|
+
import path25 from "node:path";
|
|
4804
4906
|
|
|
4805
4907
|
// src/services/egress-service.ts
|
|
4806
|
-
import { existsSync as
|
|
4807
|
-
import { mkdir as
|
|
4908
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
4909
|
+
import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
|
|
4808
4910
|
import net from "node:net";
|
|
4809
|
-
import
|
|
4911
|
+
import path15 from "node:path";
|
|
4810
4912
|
init_config_io();
|
|
4811
4913
|
init_config();
|
|
4812
4914
|
|
|
@@ -4819,16 +4921,16 @@ function egressStatePaths(repoRoot, config) {
|
|
|
4819
4921
|
const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
|
|
4820
4922
|
return {
|
|
4821
4923
|
stateDir,
|
|
4822
|
-
pidPath:
|
|
4823
|
-
statusPath:
|
|
4924
|
+
pidPath: path15.join(stateDir, "egress-proxy.pid"),
|
|
4925
|
+
statusPath: path15.join(stateDir, "egress-proxy.json")
|
|
4824
4926
|
};
|
|
4825
4927
|
}
|
|
4826
4928
|
async function readStatusFile(statusPath) {
|
|
4827
|
-
if (!
|
|
4929
|
+
if (!existsSync6(statusPath)) {
|
|
4828
4930
|
return null;
|
|
4829
4931
|
}
|
|
4830
4932
|
try {
|
|
4831
|
-
const raw = JSON.parse(await
|
|
4933
|
+
const raw = JSON.parse(await readFile6(statusPath, "utf8"));
|
|
4832
4934
|
if (typeof raw.pid !== "number") {
|
|
4833
4935
|
return null;
|
|
4834
4936
|
}
|
|
@@ -4846,9 +4948,9 @@ async function readStatusFile(statusPath) {
|
|
|
4846
4948
|
async function isPortOpen(host, port) {
|
|
4847
4949
|
return new Promise((resolve) => {
|
|
4848
4950
|
const socket = net.createConnection({ host, port });
|
|
4849
|
-
const finish = (
|
|
4951
|
+
const finish = (open7) => {
|
|
4850
4952
|
socket.destroy();
|
|
4851
|
-
resolve(
|
|
4953
|
+
resolve(open7);
|
|
4852
4954
|
};
|
|
4853
4955
|
socket.setTimeout(300);
|
|
4854
4956
|
socket.on("connect", () => finish(true));
|
|
@@ -4859,7 +4961,7 @@ async function isPortOpen(host, port) {
|
|
|
4859
4961
|
async function resolveLiveEgressStatus(repoRoot, config) {
|
|
4860
4962
|
const { statusPath } = egressStatePaths(repoRoot, config);
|
|
4861
4963
|
const statusCandidates = [statusPath];
|
|
4862
|
-
const controlPlaneStatus =
|
|
4964
|
+
const controlPlaneStatus = path15.join(configuredControlPlaneDir(config), "egress-proxy.json");
|
|
4863
4965
|
if (!statusCandidates.includes(controlPlaneStatus)) {
|
|
4864
4966
|
statusCandidates.push(controlPlaneStatus);
|
|
4865
4967
|
}
|
|
@@ -4885,7 +4987,7 @@ function isProcessAlive(pid) {
|
|
|
4885
4987
|
}
|
|
4886
4988
|
}
|
|
4887
4989
|
async function egressStatus(options = {}) {
|
|
4888
|
-
const repoRoot =
|
|
4990
|
+
const repoRoot = path15.resolve(options.targetDir ?? process.cwd());
|
|
4889
4991
|
const config = await loadConfigFile(repoRoot);
|
|
4890
4992
|
const { status, host, port, portOccupied } = await resolveLiveEgressStatus(repoRoot, config);
|
|
4891
4993
|
const ownedRunning = Boolean(status);
|
|
@@ -4916,7 +5018,7 @@ init_config();
|
|
|
4916
5018
|
import { createHash as createHash8, randomUUID as randomUUID2 } from "node:crypto";
|
|
4917
5019
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
4918
5020
|
import { access as access2, constants as constants2, lstat as lstat4, mkdtemp as mkdtemp2, realpath as realpath2, rm as rm2 } from "node:fs/promises";
|
|
4919
|
-
import
|
|
5021
|
+
import path20 from "node:path";
|
|
4920
5022
|
init_fingerprint2();
|
|
4921
5023
|
init_path_utils();
|
|
4922
5024
|
|
|
@@ -5095,7 +5197,7 @@ function runProcessWithBoundedOutput(file, args, options, timeoutMs, outputPolic
|
|
|
5095
5197
|
}
|
|
5096
5198
|
|
|
5097
5199
|
// src/core/contained-execution/docker-policy.ts
|
|
5098
|
-
import
|
|
5200
|
+
import path16 from "node:path";
|
|
5099
5201
|
|
|
5100
5202
|
// src/core/contained-execution/policy.ts
|
|
5101
5203
|
var CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS = [
|
|
@@ -5224,7 +5326,7 @@ var PROXY_ENV_NAMES = [
|
|
|
5224
5326
|
var IMAGE_ID_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
5225
5327
|
var SAFE_CONTAINER_NAME = /^belay-contained-[0-9a-f-]{36}$/;
|
|
5226
5328
|
function assertSafeDockerPath(value, code) {
|
|
5227
|
-
if (!
|
|
5329
|
+
if (!path16.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
|
|
5228
5330
|
throw new ContainedExecutionFailureError(code);
|
|
5229
5331
|
}
|
|
5230
5332
|
}
|
|
@@ -5303,11 +5405,11 @@ init_path_utils();
|
|
|
5303
5405
|
import { createHash as createHash7 } from "node:crypto";
|
|
5304
5406
|
import { constants as fsConstants } from "node:fs";
|
|
5305
5407
|
import {
|
|
5306
|
-
chmod as
|
|
5408
|
+
chmod as chmod3,
|
|
5307
5409
|
lstat as lstat3,
|
|
5308
|
-
mkdir as
|
|
5410
|
+
mkdir as mkdir6,
|
|
5309
5411
|
mkdtemp,
|
|
5310
|
-
open as
|
|
5412
|
+
open as open4,
|
|
5311
5413
|
opendir,
|
|
5312
5414
|
readlink as readlink2,
|
|
5313
5415
|
realpath,
|
|
@@ -5315,41 +5417,41 @@ import {
|
|
|
5315
5417
|
symlink
|
|
5316
5418
|
} from "node:fs/promises";
|
|
5317
5419
|
import os from "node:os";
|
|
5318
|
-
import
|
|
5420
|
+
import path19 from "node:path";
|
|
5319
5421
|
|
|
5320
5422
|
// src/core/transactional/file-tree.ts
|
|
5321
5423
|
import { createHash as createHash6 } from "node:crypto";
|
|
5322
5424
|
import { lstat as lstat2, readdir } from "node:fs/promises";
|
|
5323
|
-
import
|
|
5425
|
+
import path18 from "node:path";
|
|
5324
5426
|
|
|
5325
5427
|
// src/core/transactional/file-tree-path.ts
|
|
5326
5428
|
init_path_utils();
|
|
5327
|
-
import
|
|
5429
|
+
import path17 from "node:path";
|
|
5328
5430
|
var FILE_CHECKPOINT_PATH_ESCAPE = "file_checkpoint_path_escape";
|
|
5329
5431
|
function validateRelativePath(relativePath) {
|
|
5330
5432
|
if (!relativePath || relativePath.includes("\0")) {
|
|
5331
5433
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
5332
5434
|
}
|
|
5333
|
-
if (
|
|
5435
|
+
if (path17.isAbsolute(relativePath)) {
|
|
5334
5436
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
5335
5437
|
}
|
|
5336
5438
|
if (isPathOutsideRoot(relativePath)) {
|
|
5337
5439
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
5338
5440
|
}
|
|
5339
|
-
const normalized =
|
|
5340
|
-
if (normalized === ".." || normalized.startsWith(`..${
|
|
5441
|
+
const normalized = path17.normalize(relativePath);
|
|
5442
|
+
if (normalized === ".." || normalized.startsWith(`..${path17.sep}`)) {
|
|
5341
5443
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
5342
5444
|
}
|
|
5343
5445
|
}
|
|
5344
5446
|
function joinRelativePath(root, relativePath) {
|
|
5345
5447
|
validateRelativePath(relativePath);
|
|
5346
|
-
return
|
|
5448
|
+
return path17.join(canonicalPath(root), relativePath);
|
|
5347
5449
|
}
|
|
5348
5450
|
function isRootGitMetadataPath(relativePath) {
|
|
5349
|
-
return relativePath === ".git" || relativePath.startsWith(`.git${
|
|
5451
|
+
return relativePath === ".git" || relativePath.startsWith(`.git${path17.sep}`);
|
|
5350
5452
|
}
|
|
5351
5453
|
function isNestedGitPath(relativePath) {
|
|
5352
|
-
const segments = relativePath.split(
|
|
5454
|
+
const segments = relativePath.split(path17.sep).filter(Boolean);
|
|
5353
5455
|
if (segments.length === 0) {
|
|
5354
5456
|
return false;
|
|
5355
5457
|
}
|
|
@@ -5516,7 +5618,7 @@ async function readPresentNode(absolutePath, counters) {
|
|
|
5516
5618
|
async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters, quotas, deadlineMs, entries) {
|
|
5517
5619
|
assertWithinDeadline(deadlineMs);
|
|
5518
5620
|
assertWithinQuotas(counters, quotas);
|
|
5519
|
-
const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) :
|
|
5621
|
+
const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path18.resolve(resourceRoot);
|
|
5520
5622
|
const dirInfo = await lstat2(absoluteDir);
|
|
5521
5623
|
if (!dirInfo.isDirectory()) {
|
|
5522
5624
|
throw new Error(FILE_CHECKPOINT_UNSUPPORTED_NODE);
|
|
@@ -5542,7 +5644,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
|
|
|
5542
5644
|
const names = await readdir(absoluteDir);
|
|
5543
5645
|
for (const name of names) {
|
|
5544
5646
|
assertWithinDeadline(deadlineMs);
|
|
5545
|
-
const childRelative = relativeDir ?
|
|
5647
|
+
const childRelative = relativeDir ? path18.join(relativeDir, name) : name;
|
|
5546
5648
|
validateRelativePath(childRelative);
|
|
5547
5649
|
if (isNestedGitPath(childRelative)) {
|
|
5548
5650
|
throw new Error(FILE_CHECKPOINT_NESTED_REPOSITORY);
|
|
@@ -5550,7 +5652,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
|
|
|
5550
5652
|
if (isExcludedTreePath(childRelative, excludedRoots, resourceRoot)) {
|
|
5551
5653
|
continue;
|
|
5552
5654
|
}
|
|
5553
|
-
const childAbsolute =
|
|
5655
|
+
const childAbsolute = path18.join(absoluteDir, name);
|
|
5554
5656
|
const childInfo = await lstat2(childAbsolute);
|
|
5555
5657
|
if (childInfo.isDirectory() && !childInfo.isSymbolicLink()) {
|
|
5556
5658
|
await walkDirectory(
|
|
@@ -5661,16 +5763,16 @@ function validateContainedExecutionMirrorLease(handle, expected) {
|
|
|
5661
5763
|
);
|
|
5662
5764
|
}
|
|
5663
5765
|
var productionDependencies = {
|
|
5664
|
-
makeTempRoot: () => mkdtemp(
|
|
5766
|
+
makeTempRoot: () => mkdtemp(path19.join(os.tmpdir(), "belay-contained-mirror-")),
|
|
5665
5767
|
removeRoot: (root) => rm(root, { recursive: true, force: true }),
|
|
5666
5768
|
now: () => Date.now()
|
|
5667
5769
|
};
|
|
5668
5770
|
function isAtOrWithin(root, target) {
|
|
5669
|
-
const relative =
|
|
5670
|
-
return relative === "" || !
|
|
5771
|
+
const relative = path19.relative(root, target);
|
|
5772
|
+
return relative === "" || !path19.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path19.sep}`);
|
|
5671
5773
|
}
|
|
5672
5774
|
function isGitMetadataRelativePath(relativePath) {
|
|
5673
|
-
return relativePath.split(
|
|
5775
|
+
return relativePath.split(path19.sep).some((segment) => segment.toLowerCase() === ".git");
|
|
5674
5776
|
}
|
|
5675
5777
|
function identityFromStats(stats) {
|
|
5676
5778
|
return {
|
|
@@ -5752,7 +5854,7 @@ async function readStableFile(absolutePath, context) {
|
|
|
5752
5854
|
if (before.nlink > 1n) {
|
|
5753
5855
|
throw new Error(FILE_CHECKPOINT_HARDLINK_UNSUPPORTED);
|
|
5754
5856
|
}
|
|
5755
|
-
const source = await
|
|
5857
|
+
const source = await open4(absolutePath, safeReadFlags());
|
|
5756
5858
|
try {
|
|
5757
5859
|
const opened = await source.stat({ bigint: true });
|
|
5758
5860
|
assertRegularSingleLinkBigInt(opened);
|
|
@@ -5807,7 +5909,7 @@ function addMetadataRoots(context, directoryPath) {
|
|
|
5807
5909
|
}
|
|
5808
5910
|
}
|
|
5809
5911
|
function pathMatchesRoots(absolutePath, roots) {
|
|
5810
|
-
const lexical =
|
|
5912
|
+
const lexical = path19.resolve(absolutePath);
|
|
5811
5913
|
const canonical = canonicalPath(absolutePath);
|
|
5812
5914
|
for (const root of roots) {
|
|
5813
5915
|
if (isAtOrWithin(root, lexical) || isAtOrWithin(root, canonical)) {
|
|
@@ -5817,7 +5919,7 @@ function pathMatchesRoots(absolutePath, roots) {
|
|
|
5817
5919
|
return false;
|
|
5818
5920
|
}
|
|
5819
5921
|
function pathLexicallyMatchesRoots(absolutePath, roots) {
|
|
5820
|
-
const lexical =
|
|
5922
|
+
const lexical = path19.resolve(absolutePath);
|
|
5821
5923
|
for (const root of roots) {
|
|
5822
5924
|
if (isAtOrWithin(root, lexical)) {
|
|
5823
5925
|
return true;
|
|
@@ -5838,11 +5940,11 @@ async function readSafeSymlink(absolutePath, context) {
|
|
|
5838
5940
|
}
|
|
5839
5941
|
const identity = identityFromStats(before);
|
|
5840
5942
|
const target = await readlink2(absolutePath);
|
|
5841
|
-
if (
|
|
5943
|
+
if (path19.isAbsolute(target)) {
|
|
5842
5944
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
|
|
5843
5945
|
}
|
|
5844
|
-
const lexicalTarget =
|
|
5845
|
-
if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(
|
|
5946
|
+
const lexicalTarget = path19.resolve(path19.dirname(absolutePath), target);
|
|
5947
|
+
if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path19.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
|
|
5846
5948
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
|
|
5847
5949
|
}
|
|
5848
5950
|
let resolvedTarget;
|
|
@@ -5878,7 +5980,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
|
|
|
5878
5980
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5879
5981
|
}
|
|
5880
5982
|
const directoryFlags = safeReadFlags() | (fsConstants.O_DIRECTORY ?? 0);
|
|
5881
|
-
const directory = await
|
|
5983
|
+
const directory = await open4(absoluteDirectory, directoryFlags);
|
|
5882
5984
|
try {
|
|
5883
5985
|
const opened = await directory.stat({ bigint: true });
|
|
5884
5986
|
if (!opened.isDirectory() || !identitiesEqual(beforeIdentity, identityFromStats(opened))) {
|
|
@@ -5887,7 +5989,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
|
|
|
5887
5989
|
const entriesDirectory = await opendir(absoluteDirectory, { bufferSize: 32 });
|
|
5888
5990
|
for await (const directoryEntry of entriesDirectory) {
|
|
5889
5991
|
assertDeadline(context.deadlineMs, context.now);
|
|
5890
|
-
const relativePath = relativeDirectory ?
|
|
5992
|
+
const relativePath = relativeDirectory ? path19.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
|
|
5891
5993
|
const absolutePath = joinRelativePath(context.sourceRoot, relativePath);
|
|
5892
5994
|
const info = await lstat3(absolutePath);
|
|
5893
5995
|
if (info.isSymbolicLink()) {
|
|
@@ -5971,7 +6073,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5971
6073
|
}
|
|
5972
6074
|
assertEntryIdentity(entry, identityFromStats(before));
|
|
5973
6075
|
await context.beforeCopyOpen?.(sourcePath);
|
|
5974
|
-
const source = await
|
|
6076
|
+
const source = await open4(sourcePath, safeReadFlags());
|
|
5975
6077
|
let destination;
|
|
5976
6078
|
try {
|
|
5977
6079
|
const opened = await source.stat({ bigint: true });
|
|
@@ -5981,8 +6083,8 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5981
6083
|
if (entry.node.kind !== "file") {
|
|
5982
6084
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5983
6085
|
}
|
|
5984
|
-
await
|
|
5985
|
-
destination = await
|
|
6086
|
+
await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
|
|
6087
|
+
destination = await open4(
|
|
5986
6088
|
destinationPath,
|
|
5987
6089
|
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
|
|
5988
6090
|
safePermissionMode(opened.mode)
|
|
@@ -6024,7 +6126,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
6024
6126
|
if (entry.node.kind !== "file") {
|
|
6025
6127
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
6026
6128
|
}
|
|
6027
|
-
await
|
|
6129
|
+
await chmod3(destinationPath, safePermissionMode(entry.node.mode));
|
|
6028
6130
|
}
|
|
6029
6131
|
async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, context) {
|
|
6030
6132
|
const directoryEntries = [];
|
|
@@ -6033,7 +6135,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
6033
6135
|
const sourcePath = joinRelativePath(sourceRoot, entry.relativePath);
|
|
6034
6136
|
const destinationPath = joinRelativePath(destinationRoot, entry.relativePath);
|
|
6035
6137
|
if (entry.node.kind === "directory") {
|
|
6036
|
-
await
|
|
6138
|
+
await mkdir6(destinationPath, { recursive: true, mode: 448 });
|
|
6037
6139
|
directoryEntries.push(entry);
|
|
6038
6140
|
continue;
|
|
6039
6141
|
}
|
|
@@ -6045,7 +6147,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
6045
6147
|
if (current !== entry.node.target) {
|
|
6046
6148
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
6047
6149
|
}
|
|
6048
|
-
await
|
|
6150
|
+
await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
|
|
6049
6151
|
await symlink(entry.node.target, destinationPath);
|
|
6050
6152
|
continue;
|
|
6051
6153
|
}
|
|
@@ -6055,12 +6157,12 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
6055
6157
|
if (entry.node.kind !== "directory") {
|
|
6056
6158
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
6057
6159
|
}
|
|
6058
|
-
await
|
|
6160
|
+
await chmod3(
|
|
6059
6161
|
joinRelativePath(destinationRoot, entry.relativePath),
|
|
6060
6162
|
safePermissionMode(entry.node.mode)
|
|
6061
6163
|
);
|
|
6062
6164
|
}
|
|
6063
|
-
await
|
|
6165
|
+
await chmod3(destinationRoot, 448);
|
|
6064
6166
|
}
|
|
6065
6167
|
async function assertStableCopiedSnapshot(sourceRoot, destinationRoot, protectedRoots, limits, deadlineMs, now, before) {
|
|
6066
6168
|
const [after, copied] = await Promise.all([
|
|
@@ -6105,7 +6207,7 @@ async function cleanupOwnedRoot(root, dependencies) {
|
|
|
6105
6207
|
}
|
|
6106
6208
|
async function prepareWithDependencies(options, dependencies) {
|
|
6107
6209
|
validateOptions(options);
|
|
6108
|
-
const guestWorkspacePath =
|
|
6210
|
+
const guestWorkspacePath = path19.resolve(options.sourceRoot);
|
|
6109
6211
|
const sourceRoot = canonicalPath(options.sourceRoot);
|
|
6110
6212
|
const protectedRoots = options.controlPlaneRoots.map((root) => canonicalPath(root));
|
|
6111
6213
|
if (pathMatchesRoots(sourceRoot, protectedRoots)) {
|
|
@@ -6113,7 +6215,7 @@ async function prepareWithDependencies(options, dependencies) {
|
|
|
6113
6215
|
}
|
|
6114
6216
|
const guestRoot = await dependencies.makeTempRoot();
|
|
6115
6217
|
try {
|
|
6116
|
-
await
|
|
6218
|
+
await chmod3(guestRoot, 448);
|
|
6117
6219
|
const deadlineMs = dependencies.now() + options.limits.prepareTimeoutMs;
|
|
6118
6220
|
const before = await buildSafeMirrorSnapshot(
|
|
6119
6221
|
sourceRoot,
|
|
@@ -6141,7 +6243,7 @@ async function prepareWithDependencies(options, dependencies) {
|
|
|
6141
6243
|
dependencies.now,
|
|
6142
6244
|
before
|
|
6143
6245
|
);
|
|
6144
|
-
await
|
|
6246
|
+
await chmod3(guestRoot, 448);
|
|
6145
6247
|
const lease = {
|
|
6146
6248
|
sourceRoot,
|
|
6147
6249
|
hostMirrorRoot: guestRoot,
|
|
@@ -6275,14 +6377,14 @@ async function digestFile(file) {
|
|
|
6275
6377
|
return hash.digest("hex");
|
|
6276
6378
|
}
|
|
6277
6379
|
async function resolveConfiguredDockerSubstrate(params) {
|
|
6278
|
-
if (!
|
|
6380
|
+
if (!path20.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
|
|
6279
6381
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_binary_invalid");
|
|
6280
6382
|
}
|
|
6281
6383
|
if (!params.host.startsWith("unix:///") || /[\0\n\r]/.test(params.host)) {
|
|
6282
6384
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
|
|
6283
6385
|
}
|
|
6284
6386
|
const configuredSocket = params.host.slice("unix://".length);
|
|
6285
|
-
if (!
|
|
6387
|
+
if (!path20.isAbsolute(configuredSocket)) {
|
|
6286
6388
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
|
|
6287
6389
|
}
|
|
6288
6390
|
let binaryPath;
|
|
@@ -6543,7 +6645,7 @@ async function validatedGuestCwd(params) {
|
|
|
6543
6645
|
const requiredExclusions = [
|
|
6544
6646
|
...new Set([params.controlPlaneDir, ...params.protectedRoots].map(canonicalPath))
|
|
6545
6647
|
];
|
|
6546
|
-
if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !==
|
|
6648
|
+
if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path20.resolve(params.repoRoot) || !path20.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
|
|
6547
6649
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
6548
6650
|
const resolvedRoot = await realpath2(params.mirror.hostMirrorRoot);
|
|
6549
6651
|
const protectedRoots = [canonicalPath(params.repoRoot), ...requiredExclusions];
|
|
@@ -6555,10 +6657,10 @@ async function validatedGuestCwd(params) {
|
|
|
6555
6657
|
protectedRoots: requiredExclusions
|
|
6556
6658
|
}))
|
|
6557
6659
|
throw new ContainedExecutionFailureError("contained_execution_invalid_mirror_lease");
|
|
6558
|
-
const relative =
|
|
6660
|
+
const relative = path20.relative(params.mirror.guestWorkspacePath, path20.resolve(params.guestCwd));
|
|
6559
6661
|
let resolvedCwd;
|
|
6560
6662
|
try {
|
|
6561
|
-
resolvedCwd = await realpath2(
|
|
6663
|
+
resolvedCwd = await realpath2(path20.join(resolvedRoot, relative));
|
|
6562
6664
|
} catch {
|
|
6563
6665
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
6564
6666
|
}
|
|
@@ -6566,7 +6668,7 @@ async function validatedGuestCwd(params) {
|
|
|
6566
6668
|
if (!info.isDirectory() || !pathWithinRoot(resolvedRoot, resolvedCwd)) {
|
|
6567
6669
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
6568
6670
|
}
|
|
6569
|
-
return
|
|
6671
|
+
return path20.join(params.mirror.guestWorkspacePath, path20.relative(resolvedRoot, resolvedCwd));
|
|
6570
6672
|
}
|
|
6571
6673
|
function exactKeys(value, allowed) {
|
|
6572
6674
|
const names = new Set(allowed);
|
|
@@ -6793,19 +6895,19 @@ async function executeContainedDocker(params) {
|
|
|
6793
6895
|
init_path_utils();
|
|
6794
6896
|
import { spawn as spawn3 } from "node:child_process";
|
|
6795
6897
|
import os3 from "node:os";
|
|
6796
|
-
import
|
|
6898
|
+
import path22 from "node:path";
|
|
6797
6899
|
|
|
6798
6900
|
// src/core/transactional/apply-observed-changes.ts
|
|
6799
|
-
import { copyFile, lstat as lstat5, mkdir as
|
|
6901
|
+
import { copyFile, lstat as lstat5, mkdir as mkdir7, mkdtemp as mkdtemp3, readlink as readlink3, rm as rm3, rmdir, symlink as symlink2 } from "node:fs/promises";
|
|
6800
6902
|
import os2 from "node:os";
|
|
6801
|
-
import
|
|
6903
|
+
import path21 from "node:path";
|
|
6802
6904
|
var TRANSACTIONAL_APPLY_TOCTOU = "transactional_apply_toctou";
|
|
6803
6905
|
var TRANSACTIONAL_APPLY_CONFLICT = "transactional_apply_conflict";
|
|
6804
6906
|
var TRANSACTIONAL_APPLY_ROLLBACK_FAILED = "transactional_apply_rollback_failed";
|
|
6805
6907
|
async function chmodSafe(target, mode) {
|
|
6806
6908
|
try {
|
|
6807
|
-
const { chmod:
|
|
6808
|
-
await
|
|
6909
|
+
const { chmod: chmod5 } = await import("node:fs/promises");
|
|
6910
|
+
await chmod5(target, mode & 511);
|
|
6809
6911
|
} catch {
|
|
6810
6912
|
}
|
|
6811
6913
|
}
|
|
@@ -6839,13 +6941,13 @@ async function copyPathPreservingType(source, target) {
|
|
|
6839
6941
|
}
|
|
6840
6942
|
}
|
|
6841
6943
|
await removePathIfExists(target);
|
|
6842
|
-
await
|
|
6944
|
+
await mkdir7(path21.dirname(target), { recursive: true });
|
|
6843
6945
|
if (info.isSymbolicLink()) {
|
|
6844
6946
|
await symlink2(await readlink3(source), target);
|
|
6845
6947
|
return;
|
|
6846
6948
|
}
|
|
6847
6949
|
if (info.isDirectory() && !info.isSymbolicLink()) {
|
|
6848
|
-
await
|
|
6950
|
+
await mkdir7(target, { recursive: true, mode: info.mode & 511 });
|
|
6849
6951
|
return;
|
|
6850
6952
|
}
|
|
6851
6953
|
if (!info.isFile()) {
|
|
@@ -6855,12 +6957,12 @@ async function copyPathPreservingType(source, target) {
|
|
|
6855
6957
|
await chmodSafe(target, info.mode);
|
|
6856
6958
|
}
|
|
6857
6959
|
async function assertParentChainSafe(targetRoot, relativePath, plannedDirectories = /* @__PURE__ */ new Set()) {
|
|
6858
|
-
const segments = relativePath.split(
|
|
6960
|
+
const segments = relativePath.split(path21.sep).filter(Boolean);
|
|
6859
6961
|
if (segments.length <= 1) {
|
|
6860
6962
|
return;
|
|
6861
6963
|
}
|
|
6862
6964
|
for (let index = 1; index < segments.length; index++) {
|
|
6863
|
-
const prefix = segments.slice(0, index).join(
|
|
6965
|
+
const prefix = segments.slice(0, index).join(path21.sep);
|
|
6864
6966
|
const absolute = joinRelativePath(targetRoot, prefix);
|
|
6865
6967
|
let info;
|
|
6866
6968
|
try {
|
|
@@ -6880,8 +6982,8 @@ async function assertParentChainSafe(targetRoot, relativePath, plannedDirectorie
|
|
|
6880
6982
|
}
|
|
6881
6983
|
}
|
|
6882
6984
|
async function restorePathFromBackup(backupPath, target, rollbackRoot) {
|
|
6883
|
-
const stagingRoot = await mkdtemp3(
|
|
6884
|
-
const staged =
|
|
6985
|
+
const stagingRoot = await mkdtemp3(path21.join(rollbackRoot, "restore-"));
|
|
6986
|
+
const staged = path21.join(stagingRoot, "node");
|
|
6885
6987
|
try {
|
|
6886
6988
|
await copyPathPreservingType(backupPath, staged);
|
|
6887
6989
|
await copyPathPreservingType(staged, target);
|
|
@@ -6965,7 +7067,7 @@ async function applySingleChange(sourceRoot, targetRoot, change) {
|
|
|
6965
7067
|
if (change.before.kind !== "directory") {
|
|
6966
7068
|
await removePathIfExists(target);
|
|
6967
7069
|
}
|
|
6968
|
-
await
|
|
7070
|
+
await mkdir7(target, { recursive: true, mode: change.after.mode & 511 });
|
|
6969
7071
|
await chmodSafe(target, change.after.mode);
|
|
6970
7072
|
return;
|
|
6971
7073
|
}
|
|
@@ -6984,7 +7086,7 @@ async function applyObservedChanges(params) {
|
|
|
6984
7086
|
await assertParentChainSafe(targetRoot, change.relativePath, plannedDirectories);
|
|
6985
7087
|
await assertTargetMatches(targetRoot, change);
|
|
6986
7088
|
}
|
|
6987
|
-
const backupRoot = await mkdtemp3(
|
|
7089
|
+
const backupRoot = await mkdtemp3(path21.join(os2.tmpdir(), "belay-tx-rollback-"));
|
|
6988
7090
|
const rollbackActions = [];
|
|
6989
7091
|
let mutationAttempted = false;
|
|
6990
7092
|
let resourceIdentityChanged = false;
|
|
@@ -7003,12 +7105,12 @@ async function applyObservedChanges(params) {
|
|
|
7003
7105
|
targetExists = false;
|
|
7004
7106
|
}
|
|
7005
7107
|
if (targetExists) {
|
|
7006
|
-
const backupPath =
|
|
7108
|
+
const backupPath = path21.join(
|
|
7007
7109
|
backupRoot,
|
|
7008
7110
|
String(rollbackActions.length),
|
|
7009
7111
|
change.relativePath
|
|
7010
7112
|
);
|
|
7011
|
-
await
|
|
7113
|
+
await mkdir7(path21.dirname(backupPath), { recursive: true });
|
|
7012
7114
|
await copyPathPreservingType(target, backupPath);
|
|
7013
7115
|
await assertParentChainSafe(targetRoot, change.relativePath);
|
|
7014
7116
|
rollbackActions.push({
|
|
@@ -7107,7 +7209,7 @@ function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
|
|
|
7107
7209
|
if (ignoreRoots.length === 0) {
|
|
7108
7210
|
return false;
|
|
7109
7211
|
}
|
|
7110
|
-
const absolutePath = canonicalPath(
|
|
7212
|
+
const absolutePath = canonicalPath(path22.join(repoRoot, relativePath));
|
|
7111
7213
|
return ignoreRoots.some(
|
|
7112
7214
|
(root) => pathWithinRoot(root, absolutePath) || root === absolutePath || pathWithinRoot(absolutePath, root)
|
|
7113
7215
|
);
|
|
@@ -7145,7 +7247,7 @@ async function isDirtyWorktree(repoRoot, options) {
|
|
|
7145
7247
|
}
|
|
7146
7248
|
async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
7147
7249
|
const { mkdtemp: mkdtemp6, rm: rm10 } = await import("node:fs/promises");
|
|
7148
|
-
const worktreePath = await mkdtemp6(
|
|
7250
|
+
const worktreePath = await mkdtemp6(path22.join(os3.tmpdir(), "belay-tx-"));
|
|
7149
7251
|
await execGit(repoRoot, ["worktree", "add", "--detach", worktreePath, "HEAD"]);
|
|
7150
7252
|
return {
|
|
7151
7253
|
worktreePath,
|
|
@@ -7164,14 +7266,14 @@ async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
|
7164
7266
|
}
|
|
7165
7267
|
function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
|
|
7166
7268
|
const resolvedCwd = canonicalPath(cwd);
|
|
7167
|
-
const relative =
|
|
7168
|
-
if (isPathOutsideRoot(relative) ||
|
|
7269
|
+
const relative = path22.relative(canonicalPath(repoRoot), resolvedCwd);
|
|
7270
|
+
if (isPathOutsideRoot(relative) || path22.isAbsolute(relative)) {
|
|
7169
7271
|
return worktreePath;
|
|
7170
7272
|
}
|
|
7171
7273
|
if (relative === "") {
|
|
7172
7274
|
return worktreePath;
|
|
7173
7275
|
}
|
|
7174
|
-
return
|
|
7276
|
+
return path22.join(worktreePath, relative);
|
|
7175
7277
|
}
|
|
7176
7278
|
function runShellCommand(command, cwd, timeoutMs) {
|
|
7177
7279
|
return runProcessWithBoundedOutput(command, [], { cwd, shell: true, env: process.env }, timeoutMs);
|
|
@@ -7236,7 +7338,7 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
7236
7338
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
7237
7339
|
|
|
7238
7340
|
// src/core/capability/boundary-grant-materialize.ts
|
|
7239
|
-
import
|
|
7341
|
+
import path23 from "node:path";
|
|
7240
7342
|
|
|
7241
7343
|
// src/core/glob.ts
|
|
7242
7344
|
init_approval();
|
|
@@ -7396,7 +7498,7 @@ function grantMatchesRequest(grant, request) {
|
|
|
7396
7498
|
var BOUNDARY_GRANT_TTL_MS = 15 * 6e4;
|
|
7397
7499
|
var BOUNDARY_GRANT_ISSUER_CONTAINER = "boundary:container";
|
|
7398
7500
|
function resolveCapabilityPath(targetPath, cwd) {
|
|
7399
|
-
const joined =
|
|
7501
|
+
const joined = path23.isAbsolute(targetPath) ? targetPath : path23.join(cwd, targetPath);
|
|
7400
7502
|
return canonicalPath(joined);
|
|
7401
7503
|
}
|
|
7402
7504
|
function isPathWithinBoundaryMount(request) {
|
|
@@ -7471,7 +7573,7 @@ function materializeContainerBoundaryGrant(request, params) {
|
|
|
7471
7573
|
|
|
7472
7574
|
// src/core/capability/boundary-workspace-mount.ts
|
|
7473
7575
|
init_path_utils();
|
|
7474
|
-
import
|
|
7576
|
+
import path24 from "node:path";
|
|
7475
7577
|
var HOST_PATH_ENV_VARS = [
|
|
7476
7578
|
"BELAY_EGRESS_REPO_ROOT",
|
|
7477
7579
|
"BELAY_JUDGE_BROKER_REPO_ROOT",
|
|
@@ -7496,23 +7598,23 @@ function validateWorkspaceMount(mount) {
|
|
|
7496
7598
|
if (mount.cwdRelative.includes("\0")) {
|
|
7497
7599
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
7498
7600
|
}
|
|
7499
|
-
const normalizedRelative =
|
|
7601
|
+
const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
|
|
7500
7602
|
if (normalizedRelative === ".." || normalizedRelative.startsWith("../")) {
|
|
7501
7603
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
7502
7604
|
}
|
|
7503
|
-
if (
|
|
7605
|
+
if (path24.posix.isAbsolute(normalizedRelative)) {
|
|
7504
7606
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
7505
7607
|
}
|
|
7506
7608
|
}
|
|
7507
7609
|
function resolveGuestWorkdir(mount) {
|
|
7508
7610
|
validateWorkspaceMount(mount);
|
|
7509
7611
|
const guestTargetRoot = canonicalPath(mount.guestTargetRoot);
|
|
7510
|
-
const normalizedRelative =
|
|
7612
|
+
const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
|
|
7511
7613
|
if (normalizedRelative === "." || normalizedRelative === "") {
|
|
7512
7614
|
return guestTargetRoot;
|
|
7513
7615
|
}
|
|
7514
7616
|
const segments = normalizedRelative.split("/").filter(Boolean);
|
|
7515
|
-
return
|
|
7617
|
+
return path24.posix.join(guestTargetRoot, ...segments);
|
|
7516
7618
|
}
|
|
7517
7619
|
function buildWorkspaceMountSpec(mount) {
|
|
7518
7620
|
validateWorkspaceMount(mount);
|
|
@@ -7803,7 +7905,7 @@ function containedExecutionFresh(attestation, config, now = Date.now()) {
|
|
|
7803
7905
|
}
|
|
7804
7906
|
function boundaryAttestationPath(repoRoot, config) {
|
|
7805
7907
|
const rel = config.capability?.attestationRelPath ?? ".belay/attestation.json";
|
|
7806
|
-
return
|
|
7908
|
+
return path25.isAbsolute(rel) ? rel : path25.join(repoRoot, rel);
|
|
7807
7909
|
}
|
|
7808
7910
|
async function loadBoundaryAttestation(filePath, expectedRepoRoot, controlPlaneDir) {
|
|
7809
7911
|
try {
|
|
@@ -7908,7 +8010,7 @@ init_capability_request_hash();
|
|
|
7908
8010
|
|
|
7909
8011
|
// src/core/capability/gate-policy-shadow.ts
|
|
7910
8012
|
init_config();
|
|
7911
|
-
import
|
|
8013
|
+
import path30 from "node:path";
|
|
7912
8014
|
|
|
7913
8015
|
// src/core/verdict/judge-outbound.ts
|
|
7914
8016
|
init_scrub();
|
|
@@ -7918,7 +8020,7 @@ init_path_utils();
|
|
|
7918
8020
|
|
|
7919
8021
|
// src/core/verdict/containment.ts
|
|
7920
8022
|
init_git_resource_identity();
|
|
7921
|
-
import
|
|
8023
|
+
import path26 from "node:path";
|
|
7922
8024
|
init_path_utils();
|
|
7923
8025
|
|
|
7924
8026
|
// src/core/verdict/persistent-paths.ts
|
|
@@ -7938,7 +8040,7 @@ function expandHome(token) {
|
|
|
7938
8040
|
if (!home) {
|
|
7939
8041
|
return token;
|
|
7940
8042
|
}
|
|
7941
|
-
return token === "~" ? home :
|
|
8043
|
+
return token === "~" ? home : path26.join(home, token.slice(2));
|
|
7942
8044
|
}
|
|
7943
8045
|
return token;
|
|
7944
8046
|
}
|
|
@@ -7950,10 +8052,10 @@ function resolveTrustedPath(token, trustedCwd, trusted) {
|
|
|
7950
8052
|
return null;
|
|
7951
8053
|
}
|
|
7952
8054
|
const expanded = expandHome(token);
|
|
7953
|
-
if (
|
|
8055
|
+
if (path26.isAbsolute(expanded)) {
|
|
7954
8056
|
return canonicalPath(expanded);
|
|
7955
8057
|
}
|
|
7956
|
-
return canonicalPath(
|
|
8058
|
+
return canonicalPath(path26.resolve(trustedCwd, expanded));
|
|
7957
8059
|
}
|
|
7958
8060
|
function isGitPath(resolvedPath, repoRoot) {
|
|
7959
8061
|
if (isGitMetadataPath(resolvedPath, repoRoot)) {
|
|
@@ -8070,20 +8172,20 @@ function parseTier1Json(raw) {
|
|
|
8070
8172
|
init_judge_runtime_config();
|
|
8071
8173
|
|
|
8072
8174
|
// src/core/verdict/judge-session-kill-switch.ts
|
|
8073
|
-
import { existsSync as
|
|
8074
|
-
import { mkdir as
|
|
8075
|
-
import
|
|
8175
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
8176
|
+
import { mkdir as mkdir8, readFile as readFile7, unlink as unlink5, writeFile as writeFile4 } from "node:fs/promises";
|
|
8177
|
+
import path27 from "node:path";
|
|
8076
8178
|
var JUDGE_SESSION_KILL_FILE = "judge-session-kill.json";
|
|
8077
8179
|
function judgeSessionKillSwitchPath(stateDir) {
|
|
8078
|
-
return
|
|
8180
|
+
return path27.join(stateDir, JUDGE_SESSION_KILL_FILE);
|
|
8079
8181
|
}
|
|
8080
8182
|
async function readJudgeSessionKillSwitch(stateDir) {
|
|
8081
8183
|
const filePath = judgeSessionKillSwitchPath(stateDir);
|
|
8082
|
-
if (!
|
|
8184
|
+
if (!existsSync7(filePath)) {
|
|
8083
8185
|
return null;
|
|
8084
8186
|
}
|
|
8085
8187
|
try {
|
|
8086
|
-
const raw = JSON.parse(await
|
|
8188
|
+
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
8087
8189
|
return raw.triggered === true ? raw : null;
|
|
8088
8190
|
} catch {
|
|
8089
8191
|
return null;
|
|
@@ -8094,7 +8196,7 @@ async function isJudgeSessionKillSwitchPersisted(stateDir) {
|
|
|
8094
8196
|
return record?.triggered === true;
|
|
8095
8197
|
}
|
|
8096
8198
|
async function persistJudgeSessionKillSwitch(stateDir, reason = "shadow_mismatch") {
|
|
8097
|
-
await
|
|
8199
|
+
await mkdir8(stateDir, { recursive: true, mode: 448 });
|
|
8098
8200
|
const record = {
|
|
8099
8201
|
triggered: true,
|
|
8100
8202
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8255,10 +8357,10 @@ function recordJudgeLatency(phase, latencyMs, at = Date.now()) {
|
|
|
8255
8357
|
|
|
8256
8358
|
// src/core/verdict/judge-broker-service.ts
|
|
8257
8359
|
import { spawn as spawn6 } from "node:child_process";
|
|
8258
|
-
import { existsSync as
|
|
8259
|
-
import { unlink as
|
|
8360
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
8361
|
+
import { unlink as unlink6 } from "node:fs/promises";
|
|
8260
8362
|
import net2 from "node:net";
|
|
8261
|
-
import
|
|
8363
|
+
import path28 from "node:path";
|
|
8262
8364
|
import { fileURLToPath } from "node:url";
|
|
8263
8365
|
|
|
8264
8366
|
// src/core/verdict/judge-cli.ts
|
|
@@ -8804,10 +8906,10 @@ var JUDGE_BROKER_SESSION = "judge-broker-session.json";
|
|
|
8804
8906
|
function judgeBrokerPaths(stateDir) {
|
|
8805
8907
|
return {
|
|
8806
8908
|
stateDir,
|
|
8807
|
-
socketPath:
|
|
8808
|
-
statusPath:
|
|
8809
|
-
pidPath:
|
|
8810
|
-
sessionConfigPath:
|
|
8909
|
+
socketPath: path28.join(stateDir, JUDGE_BROKER_SOCKET),
|
|
8910
|
+
statusPath: path28.join(stateDir, JUDGE_BROKER_STATUS),
|
|
8911
|
+
pidPath: path28.join(stateDir, JUDGE_BROKER_PID),
|
|
8912
|
+
sessionConfigPath: path28.join(stateDir, JUDGE_BROKER_SESSION)
|
|
8811
8913
|
};
|
|
8812
8914
|
}
|
|
8813
8915
|
function daemonScriptPath() {
|
|
@@ -8825,12 +8927,12 @@ function useInProcessBroker(env = process.env) {
|
|
|
8825
8927
|
return Boolean(env.VITEST || env.VITEST_WORKER_ID || env.BELAY_JUDGE_BROKER_IN_PROCESS === "1");
|
|
8826
8928
|
}
|
|
8827
8929
|
async function readBrokerStatus(statusPath) {
|
|
8828
|
-
if (!
|
|
8930
|
+
if (!existsSync8(statusPath)) {
|
|
8829
8931
|
return null;
|
|
8830
8932
|
}
|
|
8831
8933
|
try {
|
|
8832
|
-
const { readFile:
|
|
8833
|
-
const raw = JSON.parse(await
|
|
8934
|
+
const { readFile: readFile17 } = await import("node:fs/promises");
|
|
8935
|
+
const raw = JSON.parse(await readFile17(statusPath, "utf8"));
|
|
8834
8936
|
if (typeof raw.pid !== "number" || typeof raw.socketPath !== "string") {
|
|
8835
8937
|
return null;
|
|
8836
8938
|
}
|
|
@@ -8840,13 +8942,13 @@ async function readBrokerStatus(statusPath) {
|
|
|
8840
8942
|
}
|
|
8841
8943
|
}
|
|
8842
8944
|
async function readBrokerSessionConfig(sessionConfigPath) {
|
|
8843
|
-
if (!
|
|
8945
|
+
if (!existsSync8(sessionConfigPath)) {
|
|
8844
8946
|
return null;
|
|
8845
8947
|
}
|
|
8846
8948
|
try {
|
|
8847
|
-
const { readFile:
|
|
8949
|
+
const { readFile: readFile17 } = await import("node:fs/promises");
|
|
8848
8950
|
const raw = JSON.parse(
|
|
8849
|
-
await
|
|
8951
|
+
await readFile17(sessionConfigPath, "utf8")
|
|
8850
8952
|
);
|
|
8851
8953
|
return normalizeJudgeSessionConfig({ ...raw, enabled: true });
|
|
8852
8954
|
} catch {
|
|
@@ -8937,7 +9039,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
|
|
|
8937
9039
|
const existingConfig = await readBrokerSessionConfig(paths.sessionConfigPath);
|
|
8938
9040
|
const configChanged = existingConfig !== null && brokerSessionConfigPayload(existingConfig) !== nextConfigPayload;
|
|
8939
9041
|
const status = await readBrokerStatus(paths.statusPath);
|
|
8940
|
-
if (status && isProcessAlive2(status.pid) &&
|
|
9042
|
+
if (status && isProcessAlive2(status.pid) && existsSync8(paths.socketPath) && !configChanged) {
|
|
8941
9043
|
await writeBrokerSessionConfig(paths, brokerSessionConfig);
|
|
8942
9044
|
return paths;
|
|
8943
9045
|
}
|
|
@@ -8959,7 +9061,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
|
|
|
8959
9061
|
child.unref();
|
|
8960
9062
|
const deadline = Date.now() + sessionConfig.connectTimeoutMs;
|
|
8961
9063
|
while (Date.now() < deadline) {
|
|
8962
|
-
if (
|
|
9064
|
+
if (existsSync8(paths.socketPath)) {
|
|
8963
9065
|
const live = await readBrokerStatus(paths.statusPath);
|
|
8964
9066
|
if (live && isProcessAlive2(live.pid)) {
|
|
8965
9067
|
return paths;
|
|
@@ -8976,8 +9078,8 @@ async function cleanupJudgeBrokerArtifacts(paths) {
|
|
|
8976
9078
|
paths.pidPath,
|
|
8977
9079
|
paths.sessionConfigPath
|
|
8978
9080
|
]) {
|
|
8979
|
-
if (
|
|
8980
|
-
await
|
|
9081
|
+
if (existsSync8(artifact)) {
|
|
9082
|
+
await unlink6(artifact).catch(() => void 0);
|
|
8981
9083
|
}
|
|
8982
9084
|
}
|
|
8983
9085
|
}
|
|
@@ -9473,9 +9575,9 @@ async function evaluateWithJudgeTransport(request, options = {}) {
|
|
|
9473
9575
|
}
|
|
9474
9576
|
|
|
9475
9577
|
// src/core/capability/gate-shadow-ratchet.ts
|
|
9476
|
-
import { existsSync as
|
|
9477
|
-
import { mkdir as
|
|
9478
|
-
import
|
|
9578
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
9579
|
+
import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
|
|
9580
|
+
import path29 from "node:path";
|
|
9479
9581
|
var DEFAULT_STATE = {
|
|
9480
9582
|
version: 1,
|
|
9481
9583
|
policyJudgeComparisons: 0,
|
|
@@ -9484,15 +9586,15 @@ var DEFAULT_STATE = {
|
|
|
9484
9586
|
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
9485
9587
|
};
|
|
9486
9588
|
function ratchetPath(stateDir) {
|
|
9487
|
-
return
|
|
9589
|
+
return path29.join(stateDir, "gate-shadow-ratchet.json");
|
|
9488
9590
|
}
|
|
9489
9591
|
async function loadState(stateDir) {
|
|
9490
9592
|
const filePath = ratchetPath(stateDir);
|
|
9491
|
-
if (!
|
|
9593
|
+
if (!existsSync9(filePath)) {
|
|
9492
9594
|
return { ...DEFAULT_STATE, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9493
9595
|
}
|
|
9494
9596
|
try {
|
|
9495
|
-
const raw = JSON.parse(await
|
|
9597
|
+
const raw = JSON.parse(await readFile8(filePath, "utf8"));
|
|
9496
9598
|
return {
|
|
9497
9599
|
...DEFAULT_STATE,
|
|
9498
9600
|
...raw,
|
|
@@ -9503,7 +9605,7 @@ async function loadState(stateDir) {
|
|
|
9503
9605
|
}
|
|
9504
9606
|
}
|
|
9505
9607
|
async function saveState(stateDir, state) {
|
|
9506
|
-
await
|
|
9608
|
+
await mkdir9(stateDir, { recursive: true, mode: 448 });
|
|
9507
9609
|
await writeFile5(
|
|
9508
9610
|
ratchetPath(stateDir),
|
|
9509
9611
|
`${JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
@@ -9626,7 +9728,7 @@ function scheduleGateShadowAudit(params) {
|
|
|
9626
9728
|
async function runGatePolicyShadowComparison(params) {
|
|
9627
9729
|
const judge = params.config.judge;
|
|
9628
9730
|
const runtime = normalizeJudgeRuntimeConfig(judge.runtime);
|
|
9629
|
-
const stateDir = params.stateDir ?? belayStateDir(params.config,
|
|
9731
|
+
const stateDir = params.stateDir ?? belayStateDir(params.config, path30.join(params.repoRoot, ".belay"));
|
|
9630
9732
|
const transport = await evaluateWithJudgeTransport(
|
|
9631
9733
|
{
|
|
9632
9734
|
prompt: buildTier1Prompt(params.command),
|
|
@@ -9916,15 +10018,15 @@ async function loadClassifierAuthorization(params) {
|
|
|
9916
10018
|
}
|
|
9917
10019
|
|
|
9918
10020
|
// src/core/capability/allowlist.ts
|
|
9919
|
-
import { existsSync as
|
|
10021
|
+
import { existsSync as existsSync10, readFileSync as readFileSync3 } from "node:fs";
|
|
9920
10022
|
init_config();
|
|
9921
10023
|
init_path_utils();
|
|
9922
|
-
import
|
|
10024
|
+
import path31 from "node:path";
|
|
9923
10025
|
function fsScopeAllowlistPath(config, repoLocalStateDir) {
|
|
9924
|
-
return
|
|
10026
|
+
return path31.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
|
|
9925
10027
|
}
|
|
9926
10028
|
function loadFsScopeAllowlistSync(filePath) {
|
|
9927
|
-
if (!
|
|
10029
|
+
if (!existsSync10(filePath)) {
|
|
9928
10030
|
return { version: 1, paths: [] };
|
|
9929
10031
|
}
|
|
9930
10032
|
const raw = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
@@ -10016,7 +10118,7 @@ function checkGatedActionLimits(action) {
|
|
|
10016
10118
|
// src/core/capability/paths.ts
|
|
10017
10119
|
init_path_utils();
|
|
10018
10120
|
init_shell_tokenizer();
|
|
10019
|
-
import
|
|
10121
|
+
import path32 from "node:path";
|
|
10020
10122
|
function applyPatchTargets(patch) {
|
|
10021
10123
|
const targets = [];
|
|
10022
10124
|
for (const line of patch.split("\n")) {
|
|
@@ -10081,7 +10183,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
|
|
|
10081
10183
|
const paths = /* @__PURE__ */ new Set();
|
|
10082
10184
|
const filePath = extractToolFilePath(payload);
|
|
10083
10185
|
if (filePath) {
|
|
10084
|
-
const resolved =
|
|
10186
|
+
const resolved = path32.isAbsolute(filePath) ? filePath : path32.resolve(cwd, filePath);
|
|
10085
10187
|
if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
|
|
10086
10188
|
paths.add(resolved);
|
|
10087
10189
|
}
|
|
@@ -10091,7 +10193,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
|
|
|
10091
10193
|
const patch = extractToolPatch(payload);
|
|
10092
10194
|
if (patch) {
|
|
10093
10195
|
for (const target of applyPatchTargets(patch)) {
|
|
10094
|
-
const resolved =
|
|
10196
|
+
const resolved = path32.isAbsolute(target) ? target : path32.resolve(cwd, target);
|
|
10095
10197
|
if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
|
|
10096
10198
|
paths.add(resolved);
|
|
10097
10199
|
}
|
|
@@ -10145,7 +10247,7 @@ function policyReasonToLegacyReason(decision) {
|
|
|
10145
10247
|
// src/core/capability/policy-engine.ts
|
|
10146
10248
|
init_git_resource_identity();
|
|
10147
10249
|
import { createHash as createHash10 } from "node:crypto";
|
|
10148
|
-
import
|
|
10250
|
+
import path33 from "node:path";
|
|
10149
10251
|
init_path_utils();
|
|
10150
10252
|
init_shell_tokenizer();
|
|
10151
10253
|
init_grant();
|
|
@@ -10437,7 +10539,7 @@ function hashSession(value) {
|
|
|
10437
10539
|
return createHash10("sha256").update(value).digest("hex").slice(0, 16);
|
|
10438
10540
|
}
|
|
10439
10541
|
function resolveCapabilityPath2(targetPath, cwd) {
|
|
10440
|
-
const joined =
|
|
10542
|
+
const joined = path33.isAbsolute(targetPath) ? targetPath : path33.join(cwd, targetPath);
|
|
10441
10543
|
return canonicalPath(joined);
|
|
10442
10544
|
}
|
|
10443
10545
|
function actionForFileMutation(analysis) {
|
|
@@ -10516,7 +10618,7 @@ function isRepoLocalPackageExec(request) {
|
|
|
10516
10618
|
return false;
|
|
10517
10619
|
}
|
|
10518
10620
|
const commandPath = canonicalPath(request.resource.command);
|
|
10519
|
-
return
|
|
10621
|
+
return path33.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
|
|
10520
10622
|
}
|
|
10521
10623
|
function isRepoLocalRoutineWrite(request, sensitivePaths) {
|
|
10522
10624
|
if (!isRepoLocalShellLocation(request)) {
|
|
@@ -10764,6 +10866,40 @@ function evaluateFileMutationPolicy(analysis, config, auth) {
|
|
|
10764
10866
|
);
|
|
10765
10867
|
return { request, decision };
|
|
10766
10868
|
}
|
|
10869
|
+
function buildFileReadCapabilityRequest(analysis) {
|
|
10870
|
+
return {
|
|
10871
|
+
version: CAPABILITY_REQUEST_VERSION,
|
|
10872
|
+
principal: {
|
|
10873
|
+
adapter: analysis.adapter,
|
|
10874
|
+
repoRoot: analysis.repoRoot,
|
|
10875
|
+
sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`)
|
|
10876
|
+
},
|
|
10877
|
+
action: "fs.read",
|
|
10878
|
+
resource: { kind: "path", path: analysis.resolvedPath },
|
|
10879
|
+
context: {
|
|
10880
|
+
cwd: analysis.cwd,
|
|
10881
|
+
inputFingerprint: analysis.inputFingerprint,
|
|
10882
|
+
hookKind: analysis.hookKind,
|
|
10883
|
+
analysisBasis: [`tool:${analysis.toolKind}`, `location:${analysis.locationLabel}`, "read"]
|
|
10884
|
+
},
|
|
10885
|
+
evidence: {
|
|
10886
|
+
level: analysis.locationLabel === "repo_local" ? "certain" : "possible",
|
|
10887
|
+
signals: [...analysis.signals, "effect.fs_read"]
|
|
10888
|
+
}
|
|
10889
|
+
};
|
|
10890
|
+
}
|
|
10891
|
+
function evaluateFileReadPolicy(analysis, config, auth) {
|
|
10892
|
+
const request = buildFileReadCapabilityRequest(analysis);
|
|
10893
|
+
const enriched = enrichAuthWithMaterializedGrants(request, config, {
|
|
10894
|
+
...auth,
|
|
10895
|
+
sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths
|
|
10896
|
+
});
|
|
10897
|
+
const decision = getDefaultPolicyEngine().evaluate(
|
|
10898
|
+
request,
|
|
10899
|
+
buildAuthorizationContext(config, analysis.trustedWorkspaceRoots, enriched)
|
|
10900
|
+
);
|
|
10901
|
+
return { request, decision };
|
|
10902
|
+
}
|
|
10767
10903
|
|
|
10768
10904
|
// src/core/capability/reasons.ts
|
|
10769
10905
|
var FS_SCOPE_REASONS = /* @__PURE__ */ new Set(["outside_repo_mutation", "outside_repo_redirect"]);
|
|
@@ -10780,15 +10916,15 @@ function shouldSkipBrokerApprovedRecord(brokerActive, approvalReason) {
|
|
|
10780
10916
|
}
|
|
10781
10917
|
|
|
10782
10918
|
// src/core/capability/trusted-workspace-roots.ts
|
|
10783
|
-
import { existsSync as
|
|
10919
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
10784
10920
|
init_config();
|
|
10785
10921
|
init_path_utils();
|
|
10786
|
-
import
|
|
10922
|
+
import path34 from "node:path";
|
|
10787
10923
|
function trustedWorkspaceRootsPath(config, repoLocalStateDir) {
|
|
10788
|
-
return
|
|
10924
|
+
return path34.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
|
|
10789
10925
|
}
|
|
10790
10926
|
function loadTrustedWorkspaceRootsSync(filePath) {
|
|
10791
|
-
if (!
|
|
10927
|
+
if (!existsSync11(filePath)) {
|
|
10792
10928
|
return { version: 1, roots: [] };
|
|
10793
10929
|
}
|
|
10794
10930
|
const raw = JSON.parse(readFileSync4(filePath, "utf8"));
|
|
@@ -10814,7 +10950,7 @@ function sanitizeTrustedWorkspaceRootEntries(input) {
|
|
|
10814
10950
|
const source = record.source === "approval" ? "approval" : void 0;
|
|
10815
10951
|
return [
|
|
10816
10952
|
{
|
|
10817
|
-
path:
|
|
10953
|
+
path: path34.resolve(record.path),
|
|
10818
10954
|
approvedAt,
|
|
10819
10955
|
approvalId,
|
|
10820
10956
|
...source ? { source } : {}
|
|
@@ -10843,7 +10979,7 @@ function isDirectoryPath(targetPath) {
|
|
|
10843
10979
|
function isBroadTrustedWorkspaceRoot(targetPath) {
|
|
10844
10980
|
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
10845
10981
|
const root = normalizeTrustedWorkspaceRootPath(targetPath);
|
|
10846
|
-
if (root === normalizeTrustedWorkspaceRootPath(
|
|
10982
|
+
if (root === normalizeTrustedWorkspaceRootPath(path34.parse(root).root)) {
|
|
10847
10983
|
return true;
|
|
10848
10984
|
}
|
|
10849
10985
|
if (home && normalizeTrustedWorkspaceRootPath(home) === root) {
|
|
@@ -10867,7 +11003,7 @@ function isHighStakesTrustedWorkspaceRoot(targetPath) {
|
|
|
10867
11003
|
return false;
|
|
10868
11004
|
}
|
|
10869
11005
|
return HOME_HIGH_STAKES_SEGMENTS.some(
|
|
10870
|
-
(segment) => pathWithinRoot(
|
|
11006
|
+
(segment) => pathWithinRoot(path34.join(homeRoot, segment), normalized)
|
|
10871
11007
|
);
|
|
10872
11008
|
}
|
|
10873
11009
|
function validateTrustedWorkspaceRootCandidate(params) {
|
|
@@ -10904,7 +11040,7 @@ function validateTrustedWorkspaceRootCandidate(params) {
|
|
|
10904
11040
|
init_config_layers();
|
|
10905
11041
|
|
|
10906
11042
|
// src/core/contained-execution/eligibility.ts
|
|
10907
|
-
import
|
|
11043
|
+
import path35 from "node:path";
|
|
10908
11044
|
init_path_utils();
|
|
10909
11045
|
var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
|
|
10910
11046
|
"command_substitution",
|
|
@@ -10921,7 +11057,7 @@ var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
|
|
|
10921
11057
|
]);
|
|
10922
11058
|
function isContainedUnknownExecutionEligible(config, action, result) {
|
|
10923
11059
|
const contained = config.sandbox.containedExecution;
|
|
10924
|
-
if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !
|
|
11060
|
+
if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !path35.isAbsolute(action.repoRoot) || !hasResolvedRepoIdentity(action.repoRoot) || !config.gates.shell || result.reason !== "unknown_local_effect" || result.axes?.location !== "repo_local" || result.assessment.external) {
|
|
10925
11061
|
return false;
|
|
10926
11062
|
}
|
|
10927
11063
|
const plan = result.effectPlan;
|
|
@@ -11152,7 +11288,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
|
|
|
11152
11288
|
}
|
|
11153
11289
|
|
|
11154
11290
|
// src/core/classify-tool.ts
|
|
11155
|
-
import
|
|
11291
|
+
import path50 from "node:path";
|
|
11156
11292
|
init_fingerprint2();
|
|
11157
11293
|
init_path_utils();
|
|
11158
11294
|
init_scrub();
|
|
@@ -11272,13 +11408,13 @@ function worstEffectDecision(decisions) {
|
|
|
11272
11408
|
|
|
11273
11409
|
// src/core/effect-ir/shell-lower.ts
|
|
11274
11410
|
init_shell_tokenizer();
|
|
11275
|
-
import
|
|
11411
|
+
import path49 from "node:path";
|
|
11276
11412
|
|
|
11277
11413
|
// src/core/verdict/docker-compose-run.ts
|
|
11278
|
-
import
|
|
11414
|
+
import path37 from "node:path";
|
|
11279
11415
|
|
|
11280
11416
|
// src/core/verdict/recursive-invocation.ts
|
|
11281
|
-
import
|
|
11417
|
+
import path36 from "node:path";
|
|
11282
11418
|
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
|
|
11283
11419
|
var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
|
|
11284
11420
|
var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
|
|
@@ -11340,7 +11476,7 @@ var OSASCRIPT_PROFILE = {
|
|
|
11340
11476
|
attachedValuePrefixes: []
|
|
11341
11477
|
};
|
|
11342
11478
|
function normalizeInterpreter(value) {
|
|
11343
|
-
return
|
|
11479
|
+
return path36.basename(value);
|
|
11344
11480
|
}
|
|
11345
11481
|
function scriptResult(interpreter, token) {
|
|
11346
11482
|
if (!token) {
|
|
@@ -11570,7 +11706,7 @@ function parseOptions(words, start, options) {
|
|
|
11570
11706
|
function decodeDockerComposeRun(tokens) {
|
|
11571
11707
|
if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
|
|
11572
11708
|
const words = tokens.filter((token) => token.kind === "word");
|
|
11573
|
-
const head =
|
|
11709
|
+
const head = path37.basename(words[0]?.value ?? "");
|
|
11574
11710
|
let index;
|
|
11575
11711
|
if (head === "docker-compose") {
|
|
11576
11712
|
index = 1;
|
|
@@ -11611,7 +11747,7 @@ function decodeDockerComposeRun(tokens) {
|
|
|
11611
11747
|
}
|
|
11612
11748
|
|
|
11613
11749
|
// src/core/verdict/egress-classify.ts
|
|
11614
|
-
import
|
|
11750
|
+
import path38 from "node:path";
|
|
11615
11751
|
var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
|
|
11616
11752
|
"aws",
|
|
11617
11753
|
"curl",
|
|
@@ -11660,7 +11796,7 @@ function isEgressToolHead(head) {
|
|
|
11660
11796
|
return EGRESS_TOOL_HEADS.has(head);
|
|
11661
11797
|
}
|
|
11662
11798
|
function decodeEgressEffects(params) {
|
|
11663
|
-
const head =
|
|
11799
|
+
const head = path38.basename(params.tokens[0] ?? "");
|
|
11664
11800
|
if (head !== "curl" && head !== "wget" && head !== "gh") {
|
|
11665
11801
|
return null;
|
|
11666
11802
|
}
|
|
@@ -11668,7 +11804,7 @@ function decodeEgressEffects(params) {
|
|
|
11668
11804
|
const provenance = { segment: params.segment };
|
|
11669
11805
|
const requirements = [];
|
|
11670
11806
|
for (const file of decoded.files) {
|
|
11671
|
-
const resolved =
|
|
11807
|
+
const resolved = path38.resolve(params.cwd, expandHome2(file));
|
|
11672
11808
|
requirements.push(
|
|
11673
11809
|
requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
|
|
11674
11810
|
"egress.explicit_file_read"
|
|
@@ -11690,7 +11826,7 @@ function decodeEgressEffects(params) {
|
|
|
11690
11826
|
if (file === "-") {
|
|
11691
11827
|
continue;
|
|
11692
11828
|
}
|
|
11693
|
-
const resolved =
|
|
11829
|
+
const resolved = path38.resolve(params.cwd, expandHome2(file));
|
|
11694
11830
|
if (resolved === "/dev/null") {
|
|
11695
11831
|
continue;
|
|
11696
11832
|
}
|
|
@@ -12168,13 +12304,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
|
|
|
12168
12304
|
if (head === "wget" && !explicitOutput) {
|
|
12169
12305
|
outputFiles.push(
|
|
12170
12306
|
...endpointOutputNames.map(
|
|
12171
|
-
(name) => outputDirectory ?
|
|
12307
|
+
(name) => outputDirectory ? path38.join(outputDirectory, name) : name
|
|
12172
12308
|
)
|
|
12173
12309
|
);
|
|
12174
12310
|
} else if (head === "curl" && remoteNameOutput) {
|
|
12175
12311
|
outputFiles.push(
|
|
12176
12312
|
...endpointOutputNames.map(
|
|
12177
|
-
(name) => outputDirectory ?
|
|
12313
|
+
(name) => outputDirectory ? path38.join(outputDirectory, name) : name
|
|
12178
12314
|
)
|
|
12179
12315
|
);
|
|
12180
12316
|
}
|
|
@@ -12188,7 +12324,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
|
|
|
12188
12324
|
outputFiles: [
|
|
12189
12325
|
...new Set(
|
|
12190
12326
|
outputFiles.map(
|
|
12191
|
-
(file) => outputDirectory && directoryEligibleOutputs.has(file) && !
|
|
12327
|
+
(file) => outputDirectory && directoryEligibleOutputs.has(file) && !path38.isAbsolute(file) ? path38.join(outputDirectory, file) : file
|
|
12192
12328
|
)
|
|
12193
12329
|
)
|
|
12194
12330
|
],
|
|
@@ -12203,7 +12339,7 @@ function remoteOutputName(spec) {
|
|
|
12203
12339
|
} catch {
|
|
12204
12340
|
pathname = spec.split(/[?#]/, 1)[0] ?? "";
|
|
12205
12341
|
}
|
|
12206
|
-
const name =
|
|
12342
|
+
const name = path38.posix.basename(pathname);
|
|
12207
12343
|
return name && name !== "/" ? name : "index.html";
|
|
12208
12344
|
}
|
|
12209
12345
|
function decodeGhGrammar(tokens) {
|
|
@@ -12368,7 +12504,7 @@ function expandHome2(value) {
|
|
|
12368
12504
|
return process.env.HOME ?? value;
|
|
12369
12505
|
}
|
|
12370
12506
|
if (value.startsWith("~/")) {
|
|
12371
|
-
return
|
|
12507
|
+
return path38.join(process.env.HOME ?? "~", value.slice(2));
|
|
12372
12508
|
}
|
|
12373
12509
|
return value;
|
|
12374
12510
|
}
|
|
@@ -12387,7 +12523,7 @@ function requirement(tag, action, resource, segment, signals) {
|
|
|
12387
12523
|
}
|
|
12388
12524
|
|
|
12389
12525
|
// src/core/verdict/git-classifier.ts
|
|
12390
|
-
import
|
|
12526
|
+
import path39 from "node:path";
|
|
12391
12527
|
init_shell_tokenizer();
|
|
12392
12528
|
var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
|
|
12393
12529
|
"--copy",
|
|
@@ -12483,7 +12619,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
12483
12619
|
var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
|
|
12484
12620
|
var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
|
|
12485
12621
|
function isGitExecutable(token) {
|
|
12486
|
-
return
|
|
12622
|
+
return path39.basename(token) === "git";
|
|
12487
12623
|
}
|
|
12488
12624
|
function takesValue(flag) {
|
|
12489
12625
|
return flag === "-C" || flag === "-c" || flag === "--git-dir" || flag === "--work-tree" || flag === "--exec-path" || flag === "--paginate" || flag === "--config-env" || flag.startsWith("-C") || flag.startsWith("-c") || flag.startsWith("--git-dir=") || flag.startsWith("--work-tree=");
|
|
@@ -12511,7 +12647,7 @@ function peelGlobalOptions(tokens, baseCwd) {
|
|
|
12511
12647
|
if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
|
|
12512
12648
|
const value = tokens[index + 1];
|
|
12513
12649
|
if (token === "-C" && value) {
|
|
12514
|
-
effectiveCwd =
|
|
12650
|
+
effectiveCwd = path39.resolve(baseCwd, value);
|
|
12515
12651
|
} else if (token === "--work-tree" && value) {
|
|
12516
12652
|
workTree = value;
|
|
12517
12653
|
} else if (token === "--git-dir" && value) {
|
|
@@ -12521,7 +12657,7 @@ function peelGlobalOptions(tokens, baseCwd) {
|
|
|
12521
12657
|
continue;
|
|
12522
12658
|
}
|
|
12523
12659
|
if (token.startsWith("-C") && token.length > 2) {
|
|
12524
|
-
effectiveCwd =
|
|
12660
|
+
effectiveCwd = path39.resolve(baseCwd, token.slice(2));
|
|
12525
12661
|
index += 1;
|
|
12526
12662
|
continue;
|
|
12527
12663
|
}
|
|
@@ -12664,7 +12800,7 @@ function looksLikeDiffPathOperand(token) {
|
|
|
12664
12800
|
if (!looksLikeFileOperand(token)) {
|
|
12665
12801
|
return false;
|
|
12666
12802
|
}
|
|
12667
|
-
if (token.startsWith(".") ||
|
|
12803
|
+
if (token.startsWith(".") || path39.isAbsolute(token)) {
|
|
12668
12804
|
return true;
|
|
12669
12805
|
}
|
|
12670
12806
|
return token.includes(".");
|
|
@@ -12672,12 +12808,12 @@ function looksLikeDiffPathOperand(token) {
|
|
|
12672
12808
|
function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
|
|
12673
12809
|
const resolveBase = effectiveCwd ?? baseCwd;
|
|
12674
12810
|
if (workTree) {
|
|
12675
|
-
return
|
|
12811
|
+
return path39.resolve(resolveBase, workTree);
|
|
12676
12812
|
}
|
|
12677
12813
|
if (gitDir) {
|
|
12678
|
-
const resolvedGitDir =
|
|
12679
|
-
if (
|
|
12680
|
-
return
|
|
12814
|
+
const resolvedGitDir = path39.resolve(resolveBase, gitDir);
|
|
12815
|
+
if (path39.basename(resolvedGitDir) === ".git") {
|
|
12816
|
+
return path39.dirname(resolvedGitDir);
|
|
12681
12817
|
}
|
|
12682
12818
|
}
|
|
12683
12819
|
return void 0;
|
|
@@ -12758,7 +12894,7 @@ function classifyGitCommand(tokens, baseCwd) {
|
|
|
12758
12894
|
const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
|
|
12759
12895
|
const normalizedKey = `git ${subcommand}`;
|
|
12760
12896
|
const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
|
|
12761
|
-
const effectiveGitDir = gitDir ?
|
|
12897
|
+
const effectiveGitDir = gitDir ? path39.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
|
|
12762
12898
|
const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
|
|
12763
12899
|
(target, index, targets) => Boolean(target) && targets.indexOf(target) === index
|
|
12764
12900
|
);
|
|
@@ -12910,9 +13046,9 @@ function decodeGitEffects(params) {
|
|
|
12910
13046
|
...subcommand === "push" ? ["tier0_external"] : []
|
|
12911
13047
|
];
|
|
12912
13048
|
const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
|
|
12913
|
-
const workTreeRoot = normalized.workTree ?
|
|
12914
|
-
const gitRefRoot = normalized.gitDir ?
|
|
12915
|
-
const gitControlRoot = normalized.gitDir ? gitRefRoot :
|
|
13049
|
+
const workTreeRoot = normalized.workTree ? path39.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
|
|
13050
|
+
const gitRefRoot = normalized.gitDir ? path39.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
|
|
13051
|
+
const gitControlRoot = normalized.gitDir ? gitRefRoot : path39.join(gitRefRoot, ".git");
|
|
12916
13052
|
const requirements = [];
|
|
12917
13053
|
if (subcommand === "fetch" || subcommand === "pull") {
|
|
12918
13054
|
const positionals = gitRemotePositionals(args);
|
|
@@ -13071,7 +13207,7 @@ function decodeGitEffects(params) {
|
|
|
13071
13207
|
gitRequirement(
|
|
13072
13208
|
"control_plane.write",
|
|
13073
13209
|
"control_plane.write",
|
|
13074
|
-
{ kind: "path", path:
|
|
13210
|
+
{ kind: "path", path: path39.join(gitControlRoot, "logs") },
|
|
13075
13211
|
params.segment,
|
|
13076
13212
|
[...signals, "git_history_destructive", "git.reflog.mutate"]
|
|
13077
13213
|
)
|
|
@@ -13129,7 +13265,7 @@ function decodeGitEffects(params) {
|
|
|
13129
13265
|
gitRequirement(
|
|
13130
13266
|
"fs.read",
|
|
13131
13267
|
"fs.read",
|
|
13132
|
-
{ kind: "path", path:
|
|
13268
|
+
{ kind: "path", path: path39.resolve(workTreeRoot, operand) },
|
|
13133
13269
|
params.segment,
|
|
13134
13270
|
[...signals, "git.path.read"]
|
|
13135
13271
|
)
|
|
@@ -13164,7 +13300,7 @@ function decodeGitEffects(params) {
|
|
|
13164
13300
|
gitRequirement(
|
|
13165
13301
|
"fs.write",
|
|
13166
13302
|
"fs.write",
|
|
13167
|
-
{ kind: "path", path:
|
|
13303
|
+
{ kind: "path", path: path39.resolve(workTreeRoot, operand) },
|
|
13168
13304
|
params.segment,
|
|
13169
13305
|
[...signals, "git.path.write"]
|
|
13170
13306
|
)
|
|
@@ -13348,8 +13484,8 @@ function gitRequirement(tag, action, resource, segment, signals) {
|
|
|
13348
13484
|
}
|
|
13349
13485
|
|
|
13350
13486
|
// src/core/verdict/launcher-resolve.ts
|
|
13351
|
-
import { existsSync as
|
|
13352
|
-
import
|
|
13487
|
+
import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
|
|
13488
|
+
import path40 from "node:path";
|
|
13353
13489
|
|
|
13354
13490
|
// src/core/verdict/makefile-expand.ts
|
|
13355
13491
|
var MAX_EXPAND_DEPTH = 16;
|
|
@@ -13522,8 +13658,8 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
13522
13658
|
"why"
|
|
13523
13659
|
]);
|
|
13524
13660
|
function readPackageJson(dir) {
|
|
13525
|
-
const packagePath =
|
|
13526
|
-
if (!
|
|
13661
|
+
const packagePath = path40.join(dir, "package.json");
|
|
13662
|
+
if (!existsSync12(packagePath)) {
|
|
13527
13663
|
return null;
|
|
13528
13664
|
}
|
|
13529
13665
|
try {
|
|
@@ -13533,17 +13669,17 @@ function readPackageJson(dir) {
|
|
|
13533
13669
|
}
|
|
13534
13670
|
}
|
|
13535
13671
|
function findPackageJson(startDir, stopDir) {
|
|
13536
|
-
let current =
|
|
13537
|
-
const stop =
|
|
13672
|
+
let current = path40.resolve(startDir);
|
|
13673
|
+
const stop = path40.resolve(stopDir);
|
|
13538
13674
|
while (true) {
|
|
13539
|
-
const packagePath =
|
|
13540
|
-
if (
|
|
13675
|
+
const packagePath = path40.join(current, "package.json");
|
|
13676
|
+
if (existsSync12(packagePath)) {
|
|
13541
13677
|
return packagePath;
|
|
13542
13678
|
}
|
|
13543
|
-
if (current === stop || current ===
|
|
13544
|
-
return
|
|
13679
|
+
if (current === stop || current === path40.dirname(current)) {
|
|
13680
|
+
return existsSync12(packagePath) ? packagePath : null;
|
|
13545
13681
|
}
|
|
13546
|
-
const parent =
|
|
13682
|
+
const parent = path40.dirname(current);
|
|
13547
13683
|
if (!parent.startsWith(stop) && parent !== current) {
|
|
13548
13684
|
}
|
|
13549
13685
|
if (parent === current) {
|
|
@@ -13600,7 +13736,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
|
|
|
13600
13736
|
}
|
|
13601
13737
|
return { recipes: [], opaque: true, reason: "package_json_missing" };
|
|
13602
13738
|
}
|
|
13603
|
-
const pkg = readPackageJson(
|
|
13739
|
+
const pkg = readPackageJson(path40.dirname(packagePath));
|
|
13604
13740
|
const scripts = pkg?.scripts;
|
|
13605
13741
|
if (!scripts || typeof scripts !== "object") {
|
|
13606
13742
|
return { recipes: [], opaque: true, reason: "package_scripts_missing" };
|
|
@@ -13691,20 +13827,20 @@ function parseMakefileRecipeContent(content) {
|
|
|
13691
13827
|
function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
13692
13828
|
const candidates = ["Makefile", "makefile", "GNUmakefile"];
|
|
13693
13829
|
let makefilePath = null;
|
|
13694
|
-
let searchDir =
|
|
13695
|
-
const stop =
|
|
13830
|
+
let searchDir = path40.resolve(cwd);
|
|
13831
|
+
const stop = path40.resolve(repoRoot);
|
|
13696
13832
|
while (true) {
|
|
13697
13833
|
for (const name of candidates) {
|
|
13698
|
-
const candidate =
|
|
13699
|
-
if (
|
|
13834
|
+
const candidate = path40.join(searchDir, name);
|
|
13835
|
+
if (existsSync12(candidate)) {
|
|
13700
13836
|
makefilePath = candidate;
|
|
13701
13837
|
break;
|
|
13702
13838
|
}
|
|
13703
13839
|
}
|
|
13704
|
-
if (makefilePath || searchDir === stop || searchDir ===
|
|
13840
|
+
if (makefilePath || searchDir === stop || searchDir === path40.dirname(searchDir)) {
|
|
13705
13841
|
break;
|
|
13706
13842
|
}
|
|
13707
|
-
searchDir =
|
|
13843
|
+
searchDir = path40.dirname(searchDir);
|
|
13708
13844
|
}
|
|
13709
13845
|
if (!makefilePath) {
|
|
13710
13846
|
return { recipes: [], opaque: true, reason: "unknown_local_effect" };
|
|
@@ -13731,7 +13867,7 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
|
13731
13867
|
}
|
|
13732
13868
|
const entry = targets.get(name);
|
|
13733
13869
|
if (!entry) {
|
|
13734
|
-
if (!
|
|
13870
|
+
if (!existsSync12(path40.resolve(path40.dirname(makefilePath), name))) {
|
|
13735
13871
|
hasUndefinedPrerequisite = true;
|
|
13736
13872
|
}
|
|
13737
13873
|
return;
|
|
@@ -13830,7 +13966,7 @@ function resolveLauncherRecipe(params) {
|
|
|
13830
13966
|
}
|
|
13831
13967
|
|
|
13832
13968
|
// src/core/verdict/parser.ts
|
|
13833
|
-
import
|
|
13969
|
+
import path41 from "node:path";
|
|
13834
13970
|
|
|
13835
13971
|
// src/core/shell-substitution.ts
|
|
13836
13972
|
function findStructuralCommandSubstitutions(command) {
|
|
@@ -14070,7 +14206,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
14070
14206
|
".sh"
|
|
14071
14207
|
]);
|
|
14072
14208
|
function normalizeHead(token) {
|
|
14073
|
-
const base =
|
|
14209
|
+
const base = path41.basename(token);
|
|
14074
14210
|
if (base && base !== "." && base !== "..") {
|
|
14075
14211
|
return base;
|
|
14076
14212
|
}
|
|
@@ -14380,7 +14516,7 @@ function isBareInterpreter(tokens) {
|
|
|
14380
14516
|
return false;
|
|
14381
14517
|
}
|
|
14382
14518
|
const scriptArg = args.find((token) => !token.startsWith("-"));
|
|
14383
|
-
if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(
|
|
14519
|
+
if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path41.extname(scriptArg))) {
|
|
14384
14520
|
return false;
|
|
14385
14521
|
}
|
|
14386
14522
|
if (scriptArg) {
|
|
@@ -14620,7 +14756,7 @@ function mergeNodes(nodes) {
|
|
|
14620
14756
|
}
|
|
14621
14757
|
|
|
14622
14758
|
// src/core/effect-ir/shell-lower/argv-delegate-gate.ts
|
|
14623
|
-
import
|
|
14759
|
+
import path42 from "node:path";
|
|
14624
14760
|
var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
|
|
14625
14761
|
"sudo",
|
|
14626
14762
|
"env",
|
|
@@ -14641,7 +14777,7 @@ function shouldApplyArgvDelegate(head, innerTokens, depth) {
|
|
|
14641
14777
|
if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
|
|
14642
14778
|
return false;
|
|
14643
14779
|
}
|
|
14644
|
-
const innerHead =
|
|
14780
|
+
const innerHead = path42.basename(innerTokens[0] ?? "");
|
|
14645
14781
|
if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
|
|
14646
14782
|
return false;
|
|
14647
14783
|
}
|
|
@@ -14748,7 +14884,7 @@ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegm
|
|
|
14748
14884
|
|
|
14749
14885
|
// src/core/effect-ir/shell-lower/tokens.ts
|
|
14750
14886
|
init_shell_tokenizer();
|
|
14751
|
-
import
|
|
14887
|
+
import path43 from "node:path";
|
|
14752
14888
|
var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
14753
14889
|
var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
|
|
14754
14890
|
var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
|
|
@@ -14892,9 +15028,9 @@ function resolvePathOperand(operand, cwd) {
|
|
|
14892
15028
|
return process.env.HOME ?? operand;
|
|
14893
15029
|
}
|
|
14894
15030
|
if (operand.startsWith("~/")) {
|
|
14895
|
-
return
|
|
15031
|
+
return path43.join(process.env.HOME ?? "~", operand.slice(2));
|
|
14896
15032
|
}
|
|
14897
|
-
return
|
|
15033
|
+
return path43.resolve(cwd, operand);
|
|
14898
15034
|
}
|
|
14899
15035
|
function isOptionToken(value) {
|
|
14900
15036
|
return value.startsWith("-") || value.startsWith("+");
|
|
@@ -14906,7 +15042,7 @@ function pipeToShell(command) {
|
|
|
14906
15042
|
return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
|
|
14907
15043
|
}
|
|
14908
15044
|
function executableBaseName(head) {
|
|
14909
|
-
return
|
|
15045
|
+
return path43.basename(head);
|
|
14910
15046
|
}
|
|
14911
15047
|
function isMetadataOnlyArgv(argv) {
|
|
14912
15048
|
return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
|
|
@@ -15025,15 +15161,22 @@ function effectChangingEnvironmentSignals(head, env, changedNames) {
|
|
|
15025
15161
|
}
|
|
15026
15162
|
|
|
15027
15163
|
// src/core/effect-ir/shell-lower/decode-process.ts
|
|
15028
|
-
import
|
|
15164
|
+
import path47 from "node:path";
|
|
15029
15165
|
|
|
15030
15166
|
// src/core/effect-ir/shell-lower/decoders/belay.ts
|
|
15031
|
-
import
|
|
15167
|
+
import path44 from "node:path";
|
|
15032
15168
|
function decodeBelay(args, repoRoot, segment) {
|
|
15033
15169
|
const [section, operation, key] = args;
|
|
15034
15170
|
const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
|
|
15035
15171
|
const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
|
|
15036
15172
|
const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
|
|
15173
|
+
const approvalAuthorityCommand = [
|
|
15174
|
+
"approval-token",
|
|
15175
|
+
"approve",
|
|
15176
|
+
"revoke",
|
|
15177
|
+
"standing-allow"
|
|
15178
|
+
].includes(section ?? "");
|
|
15179
|
+
const configTrustMutation = section === "config" && operation === "trust";
|
|
15037
15180
|
if (judgeCommand || configRead || configJudgeMutation) {
|
|
15038
15181
|
return [
|
|
15039
15182
|
processRequirement("belay", "inspect", segment, [
|
|
@@ -15042,12 +15185,23 @@ function decodeBelay(args, repoRoot, segment) {
|
|
|
15042
15185
|
])
|
|
15043
15186
|
];
|
|
15044
15187
|
}
|
|
15188
|
+
if (approvalAuthorityCommand || configTrustMutation) {
|
|
15189
|
+
return [
|
|
15190
|
+
requirement2(
|
|
15191
|
+
"control_plane.write",
|
|
15192
|
+
"control_plane.write",
|
|
15193
|
+
{ kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
|
|
15194
|
+
segment,
|
|
15195
|
+
[approvalAuthorityCommand ? "belay.approval_authority" : "belay.config_trust"]
|
|
15196
|
+
)
|
|
15197
|
+
];
|
|
15198
|
+
}
|
|
15045
15199
|
if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
|
|
15046
15200
|
return [
|
|
15047
15201
|
requirement2(
|
|
15048
15202
|
"control_plane.write",
|
|
15049
15203
|
"control_plane.write",
|
|
15050
|
-
{ kind: "path", path:
|
|
15204
|
+
{ kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
|
|
15051
15205
|
segment,
|
|
15052
15206
|
["belay.config_non_judge_mutation"]
|
|
15053
15207
|
)
|
|
@@ -15288,7 +15442,7 @@ function validDockerInfo(args) {
|
|
|
15288
15442
|
// src/core/effect-ir/shell-lower/decoders/filesystem.ts
|
|
15289
15443
|
init_git_resource_identity();
|
|
15290
15444
|
import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
|
|
15291
|
-
import
|
|
15445
|
+
import path45 from "node:path";
|
|
15292
15446
|
function decodeCopyMove(head, args, cwd, segment) {
|
|
15293
15447
|
const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
|
|
15294
15448
|
const operands = [];
|
|
@@ -15416,11 +15570,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
|
|
|
15416
15570
|
function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
|
|
15417
15571
|
try {
|
|
15418
15572
|
if (finalOperandIsSymlink) {
|
|
15419
|
-
return
|
|
15573
|
+
return path45.join(realpathSync4.native(path45.dirname(targetPath)), path45.basename(targetPath));
|
|
15420
15574
|
}
|
|
15421
15575
|
return realpathSync4.native(targetPath);
|
|
15422
15576
|
} catch {
|
|
15423
|
-
return
|
|
15577
|
+
return path45.resolve(targetPath);
|
|
15424
15578
|
}
|
|
15425
15579
|
}
|
|
15426
15580
|
function isSymbolicLink(targetPath) {
|
|
@@ -15431,8 +15585,8 @@ function isSymbolicLink(targetPath) {
|
|
|
15431
15585
|
}
|
|
15432
15586
|
}
|
|
15433
15587
|
function pathContains(ancestor, candidate) {
|
|
15434
|
-
const relative =
|
|
15435
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
15588
|
+
const relative = path45.relative(path45.resolve(ancestor), path45.resolve(candidate));
|
|
15589
|
+
return relative === "" || !relative.startsWith("..") && !path45.isAbsolute(relative);
|
|
15436
15590
|
}
|
|
15437
15591
|
function filesystemReadOperands(head, args) {
|
|
15438
15592
|
switch (head) {
|
|
@@ -15572,7 +15726,7 @@ function decodePrisma(args, env, repoRoot, segment) {
|
|
|
15572
15726
|
|
|
15573
15727
|
// src/core/effect-ir/shell-lower/decoders/ruby.ts
|
|
15574
15728
|
init_path_utils();
|
|
15575
|
-
import
|
|
15729
|
+
import path46 from "node:path";
|
|
15576
15730
|
var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
|
|
15577
15731
|
function railsReadOnlySubcommand(args) {
|
|
15578
15732
|
const subcommand = args[0];
|
|
@@ -15582,7 +15736,7 @@ function railsReadOnlySubcommand(args) {
|
|
|
15582
15736
|
return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
|
|
15583
15737
|
}
|
|
15584
15738
|
function isRubyTestScript(scriptPath) {
|
|
15585
|
-
const base =
|
|
15739
|
+
const base = path46.basename(scriptPath);
|
|
15586
15740
|
return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
|
|
15587
15741
|
}
|
|
15588
15742
|
function parseRubyTestInvocation(args) {
|
|
@@ -15996,7 +16150,7 @@ function decodeProcessOrFilesystem(params) {
|
|
|
15996
16150
|
requirement2(
|
|
15997
16151
|
"fs.read",
|
|
15998
16152
|
"fs.read",
|
|
15999
|
-
{ kind: "path", path:
|
|
16153
|
+
{ kind: "path", path: path47.resolve(cwd, syntax) },
|
|
16000
16154
|
segment,
|
|
16001
16155
|
["shell.syntax_source_read"]
|
|
16002
16156
|
)
|
|
@@ -16202,7 +16356,7 @@ function packageExecInnerIsMetadata(peel) {
|
|
|
16202
16356
|
|
|
16203
16357
|
// src/core/effect-ir/shell-lower/segment.ts
|
|
16204
16358
|
init_shell_tokenizer();
|
|
16205
|
-
import
|
|
16359
|
+
import path48 from "node:path";
|
|
16206
16360
|
var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
|
|
16207
16361
|
var SHELL_GLOB_PATTERN = /[*?[]/;
|
|
16208
16362
|
function requiresKnownCwd(requirementValue) {
|
|
@@ -16217,11 +16371,11 @@ function joinNestedOpacity(outer, nested) {
|
|
|
16217
16371
|
}
|
|
16218
16372
|
function startsLocalPostgresService(command) {
|
|
16219
16373
|
const tokens = tokenizeShell(command);
|
|
16220
|
-
return
|
|
16374
|
+
return path48.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
|
|
16221
16375
|
}
|
|
16222
16376
|
function resolveCdTransition(command, currentCwd) {
|
|
16223
16377
|
const tokens = tokenizeShell(command);
|
|
16224
|
-
if (
|
|
16378
|
+
if (path48.basename(tokens[0] ?? "") !== "cd") {
|
|
16225
16379
|
return null;
|
|
16226
16380
|
}
|
|
16227
16381
|
const target = tokens[1] ?? "~";
|
|
@@ -16384,7 +16538,7 @@ function lowerSegment(command, context) {
|
|
|
16384
16538
|
stripStructuredRedirects(lexed.tokens),
|
|
16385
16539
|
stripRedirects(parsedTokens)
|
|
16386
16540
|
);
|
|
16387
|
-
const head =
|
|
16541
|
+
const head = path49.basename(tokens[0] ?? parsed.head);
|
|
16388
16542
|
let opacity = segmentOpacity(command);
|
|
16389
16543
|
const signals = /* @__PURE__ */ new Set();
|
|
16390
16544
|
const requirements = [];
|
|
@@ -16967,17 +17121,41 @@ function verdictToClassifyResult(result, config, options = {}) {
|
|
|
16967
17121
|
|
|
16968
17122
|
// src/core/classify-tool.ts
|
|
16969
17123
|
var DEFAULT_SENSITIVE_PATHS = [".env", ".env.*", "**/credentials/**"];
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
"
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
|
|
16979
|
-
|
|
16980
|
-
|
|
17124
|
+
function toolInputRecord(payload) {
|
|
17125
|
+
const toolInput = payload.tool_input;
|
|
17126
|
+
if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
|
|
17127
|
+
return null;
|
|
17128
|
+
}
|
|
17129
|
+
return toolInput;
|
|
17130
|
+
}
|
|
17131
|
+
function hasMutationFields(input) {
|
|
17132
|
+
return typeof input.contents === "string" || typeof input.old_string === "string" || typeof input.new_string === "string" || typeof input.newContents === "string";
|
|
17133
|
+
}
|
|
17134
|
+
function inferToolPayloadEffect(payload, toolName) {
|
|
17135
|
+
if (extractShellCommand(payload)) {
|
|
17136
|
+
return "shell";
|
|
17137
|
+
}
|
|
17138
|
+
const input = toolInputRecord(payload);
|
|
17139
|
+
if (!input) {
|
|
17140
|
+
return "indeterminate";
|
|
17141
|
+
}
|
|
17142
|
+
if (hasMutationFields(input)) {
|
|
17143
|
+
return "file_mutation";
|
|
17144
|
+
}
|
|
17145
|
+
if (extractPatch(payload)) {
|
|
17146
|
+
return "apply_patch";
|
|
17147
|
+
}
|
|
17148
|
+
if (typeof input.pattern === "string" || typeof input.glob_pattern === "string") {
|
|
17149
|
+
return "search_read";
|
|
17150
|
+
}
|
|
17151
|
+
if (typeof input.file_path === "string") {
|
|
17152
|
+
return "file_read";
|
|
17153
|
+
}
|
|
17154
|
+
if (typeof input.path === "string" || typeof input.target_file === "string") {
|
|
17155
|
+
return normalizedToolName(toolName) === "delete" ? "file_delete" : "indeterminate";
|
|
17156
|
+
}
|
|
17157
|
+
return "indeterminate";
|
|
17158
|
+
}
|
|
16981
17159
|
function policyAuth(options) {
|
|
16982
17160
|
if (!options.grants && options.attestation === void 0 && options.egressProxyActive === void 0) {
|
|
16983
17161
|
return void 0;
|
|
@@ -17157,186 +17335,160 @@ function classifyFileMutationWithPolicy(params) {
|
|
|
17157
17335
|
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17158
17336
|
};
|
|
17159
17337
|
}
|
|
17160
|
-
|
|
17161
|
-
const
|
|
17162
|
-
const
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
|
|
17190
|
-
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
|
|
17235
|
-
|
|
17236
|
-
|
|
17237
|
-
|
|
17238
|
-
|
|
17239
|
-
|
|
17240
|
-
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17338
|
+
function classifyFileReadWithPolicy(params) {
|
|
17339
|
+
const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
|
|
17340
|
+
const { request, decision } = evaluateFileReadPolicy(
|
|
17341
|
+
{
|
|
17342
|
+
hookKind: "tool",
|
|
17343
|
+
toolKind: params.toolKind,
|
|
17344
|
+
filePath: params.filePath,
|
|
17345
|
+
resolvedPath: params.resolvedPath,
|
|
17346
|
+
repoRoot: params.repoRoot,
|
|
17347
|
+
cwd: params.cwd,
|
|
17348
|
+
inputFingerprint: fingerprint,
|
|
17349
|
+
signals: params.signals,
|
|
17350
|
+
locationLabel: params.locationLabel,
|
|
17351
|
+
trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
|
|
17352
|
+
sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths
|
|
17353
|
+
},
|
|
17354
|
+
params.config,
|
|
17355
|
+
policyAuth(params.options)
|
|
17356
|
+
);
|
|
17357
|
+
if (policyDecisionRequiresAsk(decision)) {
|
|
17358
|
+
return {
|
|
17359
|
+
verdict: "deny_pending_approval",
|
|
17360
|
+
reason: policyReasonToLegacyReason(decision),
|
|
17361
|
+
summary: params.filePath,
|
|
17362
|
+
fingerprint,
|
|
17363
|
+
assessment: {
|
|
17364
|
+
reversibility: "reversible",
|
|
17365
|
+
external: params.locationLabel === "outside_repo",
|
|
17366
|
+
blastRadius: params.locationLabel === "outside_repo" ? "outside the repository" : "sensitive repository file",
|
|
17367
|
+
confidence: 0.85,
|
|
17368
|
+
signals: [...params.signals, ...decision.signals]
|
|
17369
|
+
},
|
|
17370
|
+
capabilityRequests: [request],
|
|
17371
|
+
authorizationDecision: decision,
|
|
17372
|
+
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17373
|
+
};
|
|
17374
|
+
}
|
|
17375
|
+
return {
|
|
17376
|
+
verdict: "allow",
|
|
17377
|
+
reason: "effect.fs_read",
|
|
17378
|
+
summary: params.filePath,
|
|
17379
|
+
fingerprint,
|
|
17380
|
+
assessment: {
|
|
17381
|
+
reversibility: "reversible",
|
|
17382
|
+
external: false,
|
|
17383
|
+
blastRadius: "tool scope",
|
|
17384
|
+
confidence: 0.88,
|
|
17385
|
+
signals: [...params.signals, ...decision.signals]
|
|
17386
|
+
},
|
|
17387
|
+
capabilityRequests: [request],
|
|
17388
|
+
authorizationDecision: decision,
|
|
17389
|
+
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17390
|
+
};
|
|
17391
|
+
}
|
|
17392
|
+
function classifyFilePathMutation(params) {
|
|
17393
|
+
const {
|
|
17394
|
+
toolName,
|
|
17395
|
+
toolKind,
|
|
17396
|
+
filePath,
|
|
17397
|
+
repoRoot,
|
|
17398
|
+
cwd,
|
|
17399
|
+
config,
|
|
17400
|
+
options,
|
|
17401
|
+
sensitivePaths,
|
|
17402
|
+
protectedRoots,
|
|
17403
|
+
isDelete
|
|
17404
|
+
} = params;
|
|
17405
|
+
const signals = [];
|
|
17406
|
+
const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
|
|
17407
|
+
const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
|
|
17408
|
+
if (hitsProtectedRoot) {
|
|
17409
|
+
signals.push("control_plane_path");
|
|
17410
|
+
return classifyFileMutationWithPolicy({
|
|
17411
|
+
toolName,
|
|
17412
|
+
toolKind,
|
|
17413
|
+
filePath,
|
|
17414
|
+
resolvedPath,
|
|
17415
|
+
repoRoot,
|
|
17416
|
+
cwd,
|
|
17417
|
+
config,
|
|
17418
|
+
options,
|
|
17419
|
+
signals,
|
|
17420
|
+
isDelete,
|
|
17421
|
+
locationLabel: "control_plane"
|
|
17422
|
+
});
|
|
17423
|
+
}
|
|
17424
|
+
const workspaceMatch = resolveWorkspaceRootMatch(
|
|
17425
|
+
repoRoot,
|
|
17426
|
+
options.trustedWorkspaceRoots,
|
|
17427
|
+
resolvedPath
|
|
17428
|
+
);
|
|
17429
|
+
if (workspaceMatch === null) {
|
|
17430
|
+
signals.push("outside_repo_path");
|
|
17431
|
+
return classifyFileMutationWithPolicy({
|
|
17432
|
+
toolName,
|
|
17433
|
+
toolKind,
|
|
17434
|
+
filePath,
|
|
17435
|
+
resolvedPath,
|
|
17258
17436
|
repoRoot,
|
|
17259
|
-
options.trustedWorkspaceRoots,
|
|
17260
|
-
resolvedPath
|
|
17261
|
-
);
|
|
17262
|
-
if (workspaceMatch === null) {
|
|
17263
|
-
signals.push("outside_repo_path");
|
|
17264
|
-
return classifyFileMutationWithPolicy({
|
|
17265
|
-
toolName,
|
|
17266
|
-
toolKind,
|
|
17267
|
-
filePath,
|
|
17268
|
-
resolvedPath,
|
|
17269
|
-
repoRoot,
|
|
17270
|
-
cwd,
|
|
17271
|
-
config,
|
|
17272
|
-
options,
|
|
17273
|
-
signals,
|
|
17274
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17275
|
-
locationLabel: "outside_repo"
|
|
17276
|
-
});
|
|
17277
|
-
}
|
|
17278
|
-
if (workspaceMatch.kind === "trusted") {
|
|
17279
|
-
signals.push("trusted_workspace_root");
|
|
17280
|
-
}
|
|
17281
|
-
const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
|
|
17282
|
-
const workspacePrescan = mutationPrescanRequiresAsk({
|
|
17283
|
-
targets: [filePath],
|
|
17284
17437
|
cwd,
|
|
17438
|
+
config,
|
|
17439
|
+
options,
|
|
17440
|
+
signals,
|
|
17441
|
+
isDelete,
|
|
17442
|
+
locationLabel: "outside_repo"
|
|
17443
|
+
});
|
|
17444
|
+
}
|
|
17445
|
+
if (workspaceMatch.kind === "trusted") {
|
|
17446
|
+
signals.push("trusted_workspace_root");
|
|
17447
|
+
}
|
|
17448
|
+
const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
|
|
17449
|
+
const workspacePrescan = mutationPrescanRequiresAsk({
|
|
17450
|
+
targets: [filePath],
|
|
17451
|
+
cwd,
|
|
17452
|
+
repoRoot,
|
|
17453
|
+
trustedCwd,
|
|
17454
|
+
trustedWorkspaceRoots: options.trustedWorkspaceRoots,
|
|
17455
|
+
sensitivePaths
|
|
17456
|
+
});
|
|
17457
|
+
if (workspacePrescan) {
|
|
17458
|
+
return classifyFileMutationWithPolicy({
|
|
17459
|
+
toolName,
|
|
17460
|
+
toolKind,
|
|
17461
|
+
filePath,
|
|
17462
|
+
resolvedPath,
|
|
17285
17463
|
repoRoot,
|
|
17286
|
-
|
|
17287
|
-
|
|
17288
|
-
|
|
17464
|
+
cwd,
|
|
17465
|
+
config,
|
|
17466
|
+
options,
|
|
17467
|
+
signals,
|
|
17468
|
+
isDelete,
|
|
17469
|
+
locationLabel: "sensitive_path"
|
|
17289
17470
|
});
|
|
17290
|
-
|
|
17291
|
-
|
|
17292
|
-
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
filePath,
|
|
17312
|
-
resolvedPath,
|
|
17313
|
-
repoRoot,
|
|
17314
|
-
cwd,
|
|
17315
|
-
config,
|
|
17316
|
-
options,
|
|
17317
|
-
signals,
|
|
17318
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17319
|
-
locationLabel: "sensitive_path"
|
|
17320
|
-
});
|
|
17321
|
-
}
|
|
17322
|
-
if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
|
|
17323
|
-
if (isGitPath(resolvedPath, repoRoot)) {
|
|
17324
|
-
signals.push("protected_artifact");
|
|
17325
|
-
return classifyFileMutationWithPolicy({
|
|
17326
|
-
toolName,
|
|
17327
|
-
toolKind,
|
|
17328
|
-
filePath,
|
|
17329
|
-
resolvedPath,
|
|
17330
|
-
repoRoot,
|
|
17331
|
-
cwd,
|
|
17332
|
-
config,
|
|
17333
|
-
options,
|
|
17334
|
-
signals,
|
|
17335
|
-
isDelete: true,
|
|
17336
|
-
locationLabel: "sensitive_path"
|
|
17337
|
-
});
|
|
17338
|
-
}
|
|
17339
|
-
signals.push("file_delete");
|
|
17471
|
+
}
|
|
17472
|
+
const relativePath = workspaceMatch.relativePath;
|
|
17473
|
+
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
17474
|
+
signals.push("sensitive_path");
|
|
17475
|
+
return classifyFileMutationWithPolicy({
|
|
17476
|
+
toolName,
|
|
17477
|
+
toolKind,
|
|
17478
|
+
filePath,
|
|
17479
|
+
resolvedPath,
|
|
17480
|
+
repoRoot,
|
|
17481
|
+
cwd,
|
|
17482
|
+
config,
|
|
17483
|
+
options,
|
|
17484
|
+
signals,
|
|
17485
|
+
isDelete,
|
|
17486
|
+
locationLabel: "sensitive_path"
|
|
17487
|
+
});
|
|
17488
|
+
}
|
|
17489
|
+
if (isDelete) {
|
|
17490
|
+
if (isGitPath(resolvedPath, repoRoot)) {
|
|
17491
|
+
signals.push("protected_artifact");
|
|
17340
17492
|
return classifyFileMutationWithPolicy({
|
|
17341
17493
|
toolName,
|
|
17342
17494
|
toolKind,
|
|
@@ -17348,10 +17500,10 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17348
17500
|
options,
|
|
17349
17501
|
signals,
|
|
17350
17502
|
isDelete: true,
|
|
17351
|
-
locationLabel: "
|
|
17503
|
+
locationLabel: "sensitive_path"
|
|
17352
17504
|
});
|
|
17353
17505
|
}
|
|
17354
|
-
signals.push("
|
|
17506
|
+
signals.push("file_delete");
|
|
17355
17507
|
return classifyFileMutationWithPolicy({
|
|
17356
17508
|
toolName,
|
|
17357
17509
|
toolKind,
|
|
@@ -17362,11 +17514,228 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17362
17514
|
config,
|
|
17363
17515
|
options,
|
|
17364
17516
|
signals,
|
|
17365
|
-
isDelete:
|
|
17517
|
+
isDelete: true,
|
|
17366
17518
|
locationLabel: "repo_local"
|
|
17367
17519
|
});
|
|
17368
17520
|
}
|
|
17369
|
-
|
|
17521
|
+
signals.push("file_mutation");
|
|
17522
|
+
return classifyFileMutationWithPolicy({
|
|
17523
|
+
toolName,
|
|
17524
|
+
toolKind,
|
|
17525
|
+
filePath,
|
|
17526
|
+
resolvedPath,
|
|
17527
|
+
repoRoot,
|
|
17528
|
+
cwd,
|
|
17529
|
+
config,
|
|
17530
|
+
options,
|
|
17531
|
+
signals,
|
|
17532
|
+
isDelete: false,
|
|
17533
|
+
locationLabel: "repo_local"
|
|
17534
|
+
});
|
|
17535
|
+
}
|
|
17536
|
+
function classifyFilePathRead(params) {
|
|
17537
|
+
const {
|
|
17538
|
+
toolName,
|
|
17539
|
+
toolKind,
|
|
17540
|
+
filePath,
|
|
17541
|
+
repoRoot,
|
|
17542
|
+
cwd,
|
|
17543
|
+
config,
|
|
17544
|
+
options,
|
|
17545
|
+
sensitivePaths,
|
|
17546
|
+
protectedRoots
|
|
17547
|
+
} = params;
|
|
17548
|
+
const signals = ["effect.fs_read"];
|
|
17549
|
+
const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
|
|
17550
|
+
if (protectedRoots.some((root) => pathWithinRoot(root, resolvedPath))) {
|
|
17551
|
+
signals.push("control_plane_path");
|
|
17552
|
+
return classifyFileReadWithPolicy({
|
|
17553
|
+
toolName,
|
|
17554
|
+
toolKind,
|
|
17555
|
+
filePath,
|
|
17556
|
+
resolvedPath,
|
|
17557
|
+
repoRoot,
|
|
17558
|
+
cwd,
|
|
17559
|
+
config,
|
|
17560
|
+
options,
|
|
17561
|
+
signals,
|
|
17562
|
+
locationLabel: "control_plane"
|
|
17563
|
+
});
|
|
17564
|
+
}
|
|
17565
|
+
const workspaceMatch = resolveWorkspaceRootMatch(
|
|
17566
|
+
repoRoot,
|
|
17567
|
+
options.trustedWorkspaceRoots,
|
|
17568
|
+
resolvedPath
|
|
17569
|
+
);
|
|
17570
|
+
if (workspaceMatch === null) {
|
|
17571
|
+
signals.push("outside_repo_path");
|
|
17572
|
+
return classifyFileReadWithPolicy({
|
|
17573
|
+
toolName,
|
|
17574
|
+
toolKind,
|
|
17575
|
+
filePath,
|
|
17576
|
+
resolvedPath,
|
|
17577
|
+
repoRoot,
|
|
17578
|
+
cwd,
|
|
17579
|
+
config,
|
|
17580
|
+
options,
|
|
17581
|
+
signals,
|
|
17582
|
+
locationLabel: "outside_repo"
|
|
17583
|
+
});
|
|
17584
|
+
}
|
|
17585
|
+
const relativePath = workspaceMatch.relativePath;
|
|
17586
|
+
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
17587
|
+
signals.push("sensitive_path");
|
|
17588
|
+
return classifyFileReadWithPolicy({
|
|
17589
|
+
toolName,
|
|
17590
|
+
toolKind,
|
|
17591
|
+
filePath,
|
|
17592
|
+
resolvedPath,
|
|
17593
|
+
repoRoot,
|
|
17594
|
+
cwd,
|
|
17595
|
+
config,
|
|
17596
|
+
options,
|
|
17597
|
+
signals,
|
|
17598
|
+
locationLabel: "sensitive_path"
|
|
17599
|
+
});
|
|
17600
|
+
}
|
|
17601
|
+
return classifyFileReadWithPolicy({
|
|
17602
|
+
toolName,
|
|
17603
|
+
toolKind,
|
|
17604
|
+
filePath,
|
|
17605
|
+
resolvedPath,
|
|
17606
|
+
repoRoot,
|
|
17607
|
+
cwd,
|
|
17608
|
+
config,
|
|
17609
|
+
options,
|
|
17610
|
+
signals,
|
|
17611
|
+
locationLabel: "repo_local"
|
|
17612
|
+
});
|
|
17613
|
+
}
|
|
17614
|
+
function indeterminateToolResult(toolName, payload, repoRoot, options) {
|
|
17615
|
+
const summary = canonicalStringify(scrubPayload(payload.tool_input ?? {}, options));
|
|
17616
|
+
const fingerprint = toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot);
|
|
17617
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17618
|
+
return {
|
|
17619
|
+
verdict: "deny_pending_approval",
|
|
17620
|
+
reason: "indeterminate_tool_effect",
|
|
17621
|
+
summary,
|
|
17622
|
+
fingerprint,
|
|
17623
|
+
assessment: {
|
|
17624
|
+
reversibility: "irreversible",
|
|
17625
|
+
external: false,
|
|
17626
|
+
blastRadius: "unknown tool action",
|
|
17627
|
+
confidence: 0.5,
|
|
17628
|
+
signals: ["indeterminate_tool_effect"]
|
|
17629
|
+
}
|
|
17630
|
+
};
|
|
17631
|
+
}
|
|
17632
|
+
return {
|
|
17633
|
+
verdict: "allow_flagged",
|
|
17634
|
+
reason: "indeterminate_tool_effect",
|
|
17635
|
+
summary,
|
|
17636
|
+
fingerprint,
|
|
17637
|
+
assessment: {
|
|
17638
|
+
reversibility: "recoverable_with_cost",
|
|
17639
|
+
external: false,
|
|
17640
|
+
blastRadius: "tool scope",
|
|
17641
|
+
confidence: 0.5,
|
|
17642
|
+
signals: ["indeterminate_tool_effect"]
|
|
17643
|
+
}
|
|
17644
|
+
};
|
|
17645
|
+
}
|
|
17646
|
+
async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
17647
|
+
const toolName = String(payload.tool_name ?? "");
|
|
17648
|
+
const toolKind = normalizedToolName(toolName);
|
|
17649
|
+
const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
|
|
17650
|
+
const protectedRoots = [
|
|
17651
|
+
...options.protectedArtifactRoots ?? [],
|
|
17652
|
+
...options.controlPlaneDir ? [options.controlPlaneDir] : []
|
|
17653
|
+
];
|
|
17654
|
+
if (toolKind === "shell") {
|
|
17655
|
+
const command = extractShellCommand(payload);
|
|
17656
|
+
if (!command) {
|
|
17657
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17658
|
+
return {
|
|
17659
|
+
verdict: "deny_pending_approval",
|
|
17660
|
+
reason: "tool_shell_missing_command",
|
|
17661
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17662
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17663
|
+
assessment: {
|
|
17664
|
+
reversibility: "irreversible",
|
|
17665
|
+
external: false,
|
|
17666
|
+
blastRadius: "tool shell",
|
|
17667
|
+
confidence: 0.85,
|
|
17668
|
+
signals: ["missing_command"]
|
|
17669
|
+
}
|
|
17670
|
+
};
|
|
17671
|
+
}
|
|
17672
|
+
return {
|
|
17673
|
+
verdict: "allow_flagged",
|
|
17674
|
+
reason: "tool_shell_missing_command",
|
|
17675
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17676
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17677
|
+
assessment: {
|
|
17678
|
+
reversibility: "recoverable_with_cost",
|
|
17679
|
+
external: false,
|
|
17680
|
+
blastRadius: "tool shell",
|
|
17681
|
+
confidence: 0.5,
|
|
17682
|
+
signals: ["missing_command"]
|
|
17683
|
+
}
|
|
17684
|
+
};
|
|
17685
|
+
}
|
|
17686
|
+
const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
|
|
17687
|
+
return {
|
|
17688
|
+
...shellResult,
|
|
17689
|
+
summary: command
|
|
17690
|
+
};
|
|
17691
|
+
}
|
|
17692
|
+
const effect = inferToolPayloadEffect(payload, toolName);
|
|
17693
|
+
if (effect === "file_mutation" || effect === "file_delete") {
|
|
17694
|
+
const filePath2 = extractFilePath(payload);
|
|
17695
|
+
if (!filePath2) {
|
|
17696
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17697
|
+
return {
|
|
17698
|
+
verdict: "deny_pending_approval",
|
|
17699
|
+
reason: "file_mutation_missing_path",
|
|
17700
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17701
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17702
|
+
assessment: {
|
|
17703
|
+
reversibility: "irreversible",
|
|
17704
|
+
external: false,
|
|
17705
|
+
blastRadius: "file mutation",
|
|
17706
|
+
confidence: 0.85,
|
|
17707
|
+
signals: ["missing_path"]
|
|
17708
|
+
}
|
|
17709
|
+
};
|
|
17710
|
+
}
|
|
17711
|
+
return {
|
|
17712
|
+
verdict: "allow_flagged",
|
|
17713
|
+
reason: "file_mutation_missing_path",
|
|
17714
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17715
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17716
|
+
assessment: {
|
|
17717
|
+
reversibility: "recoverable_with_cost",
|
|
17718
|
+
external: false,
|
|
17719
|
+
blastRadius: "file mutation",
|
|
17720
|
+
confidence: 0.55,
|
|
17721
|
+
signals: ["missing_path"]
|
|
17722
|
+
}
|
|
17723
|
+
};
|
|
17724
|
+
}
|
|
17725
|
+
return classifyFilePathMutation({
|
|
17726
|
+
toolName,
|
|
17727
|
+
toolKind,
|
|
17728
|
+
filePath: filePath2,
|
|
17729
|
+
repoRoot,
|
|
17730
|
+
cwd,
|
|
17731
|
+
config,
|
|
17732
|
+
options,
|
|
17733
|
+
sensitivePaths,
|
|
17734
|
+
protectedRoots,
|
|
17735
|
+
isDelete: effect === "file_delete"
|
|
17736
|
+
});
|
|
17737
|
+
}
|
|
17738
|
+
if (effect === "apply_patch") {
|
|
17370
17739
|
const patch = extractPatch(payload);
|
|
17371
17740
|
const targets = patch ? applyPatchTargets2(patch) : [];
|
|
17372
17741
|
if (targets.length === 0) {
|
|
@@ -17404,7 +17773,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17404
17773
|
const result = await classifyToolUse(
|
|
17405
17774
|
{
|
|
17406
17775
|
tool_name: target.delete ? "Delete" : "Write",
|
|
17407
|
-
tool_input: { path: target.path }
|
|
17776
|
+
tool_input: target.delete ? { path: target.path } : { path: target.path, contents: " " }
|
|
17408
17777
|
},
|
|
17409
17778
|
repoRoot,
|
|
17410
17779
|
cwd,
|
|
@@ -17430,19 +17799,54 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17430
17799
|
}
|
|
17431
17800
|
};
|
|
17432
17801
|
}
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17438
|
-
assessment: {
|
|
17439
|
-
reversibility: "reversible",
|
|
17440
|
-
external: false,
|
|
17441
|
-
blastRadius: "tool scope",
|
|
17442
|
-
confidence: 0.5,
|
|
17443
|
-
signals: ["unclassified_tool"]
|
|
17802
|
+
if (effect === "file_read") {
|
|
17803
|
+
const filePath2 = extractFilePath(payload);
|
|
17804
|
+
if (!filePath2) {
|
|
17805
|
+
return indeterminateToolResult(toolName, payload, repoRoot, options);
|
|
17444
17806
|
}
|
|
17445
|
-
|
|
17807
|
+
return classifyFilePathRead({
|
|
17808
|
+
toolName,
|
|
17809
|
+
toolKind,
|
|
17810
|
+
filePath: filePath2,
|
|
17811
|
+
repoRoot,
|
|
17812
|
+
cwd,
|
|
17813
|
+
config,
|
|
17814
|
+
options,
|
|
17815
|
+
sensitivePaths,
|
|
17816
|
+
protectedRoots
|
|
17817
|
+
});
|
|
17818
|
+
}
|
|
17819
|
+
if (effect === "search_read") {
|
|
17820
|
+
return {
|
|
17821
|
+
verdict: "allow",
|
|
17822
|
+
reason: "effect.search_read",
|
|
17823
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17824
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17825
|
+
assessment: {
|
|
17826
|
+
reversibility: "reversible",
|
|
17827
|
+
external: false,
|
|
17828
|
+
blastRadius: "tool scope",
|
|
17829
|
+
confidence: 0.88,
|
|
17830
|
+
signals: ["effect.search_read"]
|
|
17831
|
+
}
|
|
17832
|
+
};
|
|
17833
|
+
}
|
|
17834
|
+
const filePath = extractFilePath(payload);
|
|
17835
|
+
if (filePath) {
|
|
17836
|
+
return classifyFilePathMutation({
|
|
17837
|
+
toolName,
|
|
17838
|
+
toolKind,
|
|
17839
|
+
filePath,
|
|
17840
|
+
repoRoot,
|
|
17841
|
+
cwd,
|
|
17842
|
+
config,
|
|
17843
|
+
options,
|
|
17844
|
+
sensitivePaths,
|
|
17845
|
+
protectedRoots,
|
|
17846
|
+
isDelete: toolKind === "delete"
|
|
17847
|
+
});
|
|
17848
|
+
}
|
|
17849
|
+
return indeterminateToolResult(toolName, payload, repoRoot, options);
|
|
17446
17850
|
}
|
|
17447
17851
|
|
|
17448
17852
|
// src/core/gate-engine.ts
|
|
@@ -17777,15 +18181,8 @@ function gateEnabledForAction(config, action) {
|
|
|
17777
18181
|
if (action.kind === "subagent") {
|
|
17778
18182
|
return config.gates.subagent;
|
|
17779
18183
|
}
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
return config.gates.toolShell;
|
|
17783
|
-
}
|
|
17784
|
-
if (toolName === "Write" || toolName === "StrReplace" || toolName === "Delete") {
|
|
17785
|
-
return config.gates.fileMutation;
|
|
17786
|
-
}
|
|
17787
|
-
if (toolName === "Task") {
|
|
17788
|
-
return config.gates.subagent;
|
|
18184
|
+
if (action.kind === "tool") {
|
|
18185
|
+
return config.gates.fileMutation || config.gates.toolShell;
|
|
17789
18186
|
}
|
|
17790
18187
|
return true;
|
|
17791
18188
|
}
|
|
@@ -17822,7 +18219,7 @@ function hashDecisionConfig(config) {
|
|
|
17822
18219
|
init_fingerprint2();
|
|
17823
18220
|
|
|
17824
18221
|
// src/version.ts
|
|
17825
|
-
var PACKAGE_VERSION = "0.
|
|
18222
|
+
var PACKAGE_VERSION = "0.10.1";
|
|
17826
18223
|
|
|
17827
18224
|
// src/runtime-provenance.ts
|
|
17828
18225
|
function resolveRuntimeArtifactHash(artifactHash) {
|
|
@@ -17895,56 +18292,56 @@ function capabilityRequestsBlockRecovery(requests) {
|
|
|
17895
18292
|
init_fingerprint2();
|
|
17896
18293
|
init_path_utils();
|
|
17897
18294
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
17898
|
-
import { existsSync as
|
|
17899
|
-
import { mkdir as
|
|
17900
|
-
import
|
|
18295
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
18296
|
+
import { mkdir as mkdir12, readdir as readdir3, readFile as readFile13, rename as rename4, rm as rm6 } from "node:fs/promises";
|
|
18297
|
+
import path55 from "node:path";
|
|
17901
18298
|
|
|
17902
18299
|
// src/core/recovery/artifact-store.ts
|
|
17903
18300
|
init_fingerprint2();
|
|
17904
18301
|
init_path_utils();
|
|
17905
18302
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
17906
|
-
import { existsSync as
|
|
17907
|
-
import { lstat as lstat7, mkdir as
|
|
17908
|
-
import
|
|
18303
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
18304
|
+
import { lstat as lstat7, mkdir as mkdir11, open as open6, readdir as readdir2, readFile as readFile10, rename as rename3, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
|
|
18305
|
+
import path52 from "node:path";
|
|
17909
18306
|
|
|
17910
18307
|
// src/core/recovery/snapshot-node.ts
|
|
17911
18308
|
init_fingerprint2();
|
|
17912
18309
|
init_path_utils();
|
|
17913
18310
|
import { createHash as createHash12 } from "node:crypto";
|
|
17914
|
-
import { existsSync as
|
|
18311
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
17915
18312
|
import {
|
|
17916
|
-
chmod as
|
|
18313
|
+
chmod as chmod4,
|
|
17917
18314
|
copyFile as copyFile2,
|
|
17918
18315
|
lstat as lstat6,
|
|
17919
|
-
mkdir as
|
|
17920
|
-
open as
|
|
17921
|
-
readFile as
|
|
18316
|
+
mkdir as mkdir10,
|
|
18317
|
+
open as open5,
|
|
18318
|
+
readFile as readFile9,
|
|
17922
18319
|
readlink as readlink4,
|
|
17923
18320
|
rm as rm4,
|
|
17924
18321
|
rmdir as rmdir2,
|
|
17925
18322
|
symlink as symlink3,
|
|
17926
18323
|
writeFile as writeFile6
|
|
17927
18324
|
} from "node:fs/promises";
|
|
17928
|
-
import
|
|
18325
|
+
import path51 from "node:path";
|
|
17929
18326
|
var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
|
|
17930
18327
|
function validRecoveryRelativePath(relativePath) {
|
|
17931
|
-
if (!relativePath || relativePath.includes("\0") ||
|
|
17932
|
-
const normalized =
|
|
17933
|
-
return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${
|
|
18328
|
+
if (!relativePath || relativePath.includes("\0") || path51.isAbsolute(relativePath)) return false;
|
|
18329
|
+
const normalized = path51.normalize(relativePath);
|
|
18330
|
+
return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path51.sep}`);
|
|
17934
18331
|
}
|
|
17935
18332
|
async function assertRecoverySafeTarget(resourceRoot, relativePath) {
|
|
17936
18333
|
if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
|
|
17937
18334
|
const root = canonicalPath(resourceRoot);
|
|
17938
|
-
const target =
|
|
17939
|
-
const relative =
|
|
17940
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
18335
|
+
const target = path51.resolve(root, relativePath);
|
|
18336
|
+
const relative = path51.relative(root, target);
|
|
18337
|
+
if (relative === ".." || relative.startsWith(`..${path51.sep}`) || path51.isAbsolute(relative)) {
|
|
17941
18338
|
throw new Error("recovery_path_escape");
|
|
17942
18339
|
}
|
|
17943
18340
|
let current = root;
|
|
17944
|
-
const parentParts =
|
|
18341
|
+
const parentParts = path51.relative(root, path51.dirname(target)).split(path51.sep).filter(Boolean);
|
|
17945
18342
|
for (const part of parentParts) {
|
|
17946
|
-
current =
|
|
17947
|
-
if (!
|
|
18343
|
+
current = path51.join(current, part);
|
|
18344
|
+
if (!existsSync13(current)) break;
|
|
17948
18345
|
const info = await lstat6(current);
|
|
17949
18346
|
if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
|
|
17950
18347
|
if (!info.isDirectory()) break;
|
|
@@ -17952,7 +18349,7 @@ async function assertRecoverySafeTarget(resourceRoot, relativePath) {
|
|
|
17952
18349
|
return target;
|
|
17953
18350
|
}
|
|
17954
18351
|
async function fsyncPath(filePath) {
|
|
17955
|
-
const handle = await
|
|
18352
|
+
const handle = await open5(filePath, "r");
|
|
17956
18353
|
try {
|
|
17957
18354
|
await handle.sync();
|
|
17958
18355
|
} finally {
|
|
@@ -17994,13 +18391,13 @@ async function captureRecoverySnapshot(filePath, options) {
|
|
|
17994
18391
|
return { kind: "directory", mode, hash: recoveryDirectoryHash(mode) };
|
|
17995
18392
|
}
|
|
17996
18393
|
if (!info.isFile()) throw new Error(RECOVERY_UNSUPPORTED_FILE_KIND);
|
|
17997
|
-
const content = await
|
|
18394
|
+
const content = await readFile9(filePath);
|
|
17998
18395
|
const hash = createHash12("sha256").update(content).digest("hex");
|
|
17999
18396
|
let blob;
|
|
18000
18397
|
if (options?.blobDir) {
|
|
18001
|
-
await
|
|
18002
|
-
const blobPath =
|
|
18003
|
-
if (!
|
|
18398
|
+
await mkdir10(options.blobDir, { recursive: true, mode: 448 });
|
|
18399
|
+
const blobPath = path51.join(options.blobDir, hash);
|
|
18400
|
+
if (!existsSync13(blobPath)) {
|
|
18004
18401
|
await writeFile6(blobPath, content, { mode: 384 });
|
|
18005
18402
|
await fsyncPath(blobPath);
|
|
18006
18403
|
}
|
|
@@ -18054,7 +18451,7 @@ async function validateRecoverySnapshot(params) {
|
|
|
18054
18451
|
if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
|
|
18055
18452
|
let content;
|
|
18056
18453
|
try {
|
|
18057
|
-
content = await
|
|
18454
|
+
content = await readFile9(path51.join(params.artifactDir, record.blob));
|
|
18058
18455
|
} catch {
|
|
18059
18456
|
throw new Error(params.corruptReason);
|
|
18060
18457
|
}
|
|
@@ -18082,16 +18479,16 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
|
|
|
18082
18479
|
]);
|
|
18083
18480
|
var STAGING_STALE_MS = 5 * 6e4;
|
|
18084
18481
|
function checkpointsRoot(stateDir) {
|
|
18085
|
-
return
|
|
18482
|
+
return path52.join(stateDir, "recovery", "checkpoints");
|
|
18086
18483
|
}
|
|
18087
18484
|
function checkpointDir(stateDir, checkpointId) {
|
|
18088
18485
|
if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
|
|
18089
18486
|
throw new Error("invalid_recovery_checkpoint_id");
|
|
18090
18487
|
}
|
|
18091
|
-
return
|
|
18488
|
+
return path52.join(checkpointsRoot(stateDir), checkpointId);
|
|
18092
18489
|
}
|
|
18093
18490
|
async function fsyncPath2(filePath) {
|
|
18094
|
-
const handle = await
|
|
18491
|
+
const handle = await open6(filePath, "r");
|
|
18095
18492
|
try {
|
|
18096
18493
|
await handle.sync();
|
|
18097
18494
|
} finally {
|
|
@@ -18099,13 +18496,13 @@ async function fsyncPath2(filePath) {
|
|
|
18099
18496
|
}
|
|
18100
18497
|
}
|
|
18101
18498
|
async function atomicWriteJson(filePath, value) {
|
|
18102
|
-
await
|
|
18499
|
+
await mkdir11(path52.dirname(filePath), { recursive: true, mode: 448 });
|
|
18103
18500
|
const temporary = `${filePath}.tmp-${randomUUID4()}`;
|
|
18104
18501
|
await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
|
|
18105
18502
|
`, { mode: 384 });
|
|
18106
18503
|
await fsyncPath2(temporary);
|
|
18107
|
-
await
|
|
18108
|
-
await fsyncPath2(
|
|
18504
|
+
await rename3(temporary, filePath);
|
|
18505
|
+
await fsyncPath2(path52.dirname(filePath));
|
|
18109
18506
|
}
|
|
18110
18507
|
async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
|
|
18111
18508
|
const value = {
|
|
@@ -18115,13 +18512,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
|
|
|
18115
18512
|
manifestHash,
|
|
18116
18513
|
...detail ? { detail } : {}
|
|
18117
18514
|
};
|
|
18118
|
-
await atomicWriteJson(
|
|
18515
|
+
await atomicWriteJson(path52.join(artifactDir, "state.json"), value);
|
|
18119
18516
|
}
|
|
18120
18517
|
async function directorySize(root) {
|
|
18121
|
-
if (!
|
|
18518
|
+
if (!existsSync14(root)) return 0;
|
|
18122
18519
|
let total = 0;
|
|
18123
18520
|
for (const entry of await readdir2(root, { withFileTypes: true })) {
|
|
18124
|
-
const entryPath =
|
|
18521
|
+
const entryPath = path52.join(root, entry.name);
|
|
18125
18522
|
if (entry.isDirectory()) total += await directorySize(entryPath);
|
|
18126
18523
|
else total += (await lstat7(entryPath)).size;
|
|
18127
18524
|
}
|
|
@@ -18166,9 +18563,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18166
18563
|
let rawManifest;
|
|
18167
18564
|
let state;
|
|
18168
18565
|
try {
|
|
18169
|
-
rawManifest = JSON.parse(await
|
|
18566
|
+
rawManifest = JSON.parse(await readFile10(path52.join(artifactDir, "manifest.json"), "utf8"));
|
|
18170
18567
|
state = JSON.parse(
|
|
18171
|
-
await
|
|
18568
|
+
await readFile10(path52.join(artifactDir, "state.json"), "utf8")
|
|
18172
18569
|
);
|
|
18173
18570
|
} catch {
|
|
18174
18571
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
@@ -18184,10 +18581,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18184
18581
|
}
|
|
18185
18582
|
const entryPaths = /* @__PURE__ */ new Set();
|
|
18186
18583
|
for (const entry of manifest.entries) {
|
|
18187
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(
|
|
18584
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry) || Object.keys(entry).length !== 3 || !Object.keys(entry).every((key) => ["path", "before", "after"].includes(key)) || typeof entry.path !== "string" || !("before" in entry) || !("after" in entry) || !validRecoveryRelativePath(entry.path) || entryPaths.has(path52.normalize(entry.path))) {
|
|
18188
18585
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
18189
18586
|
}
|
|
18190
|
-
entryPaths.add(
|
|
18587
|
+
entryPaths.add(path52.normalize(entry.path));
|
|
18191
18588
|
for (const [side, snapshot] of [
|
|
18192
18589
|
["before", entry.before],
|
|
18193
18590
|
["after", entry.after]
|
|
@@ -18201,9 +18598,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18201
18598
|
});
|
|
18202
18599
|
}
|
|
18203
18600
|
}
|
|
18204
|
-
const receiptPath =
|
|
18601
|
+
const receiptPath = path52.join(artifactDir, "receipt.json");
|
|
18205
18602
|
let receipt;
|
|
18206
|
-
if (["applied", "restoring", "restored", "conflict"].includes(state.state) ||
|
|
18603
|
+
if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync14(receiptPath)) {
|
|
18207
18604
|
receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
|
|
18208
18605
|
}
|
|
18209
18606
|
return { artifactDir, manifest, state, manifestHash, ...receipt ? { receipt } : {} };
|
|
@@ -18211,7 +18608,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18211
18608
|
async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
18212
18609
|
let rawReceipt;
|
|
18213
18610
|
try {
|
|
18214
|
-
rawReceipt = JSON.parse(await
|
|
18611
|
+
rawReceipt = JSON.parse(await readFile10(path52.join(artifactDir, "receipt.json"), "utf8"));
|
|
18215
18612
|
} catch {
|
|
18216
18613
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
18217
18614
|
}
|
|
@@ -18234,8 +18631,8 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
|
|
|
18234
18631
|
return receipt;
|
|
18235
18632
|
}
|
|
18236
18633
|
async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
18237
|
-
const receiptPath =
|
|
18238
|
-
if (
|
|
18634
|
+
const receiptPath = path52.join(artifactDir, "receipt.json");
|
|
18635
|
+
if (existsSync14(receiptPath)) {
|
|
18239
18636
|
return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
|
|
18240
18637
|
}
|
|
18241
18638
|
const receipt = {
|
|
@@ -18252,14 +18649,14 @@ async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
|
18252
18649
|
}
|
|
18253
18650
|
async function checkpointIds(stateDir) {
|
|
18254
18651
|
const root = checkpointsRoot(stateDir);
|
|
18255
|
-
if (!
|
|
18652
|
+
if (!existsSync14(root)) return [];
|
|
18256
18653
|
return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^cp_[a-f0-9]{24}$/.test(entry.name)).map((entry) => entry.name);
|
|
18257
18654
|
}
|
|
18258
18655
|
async function artifactRepoRoot(stateDir, checkpointId) {
|
|
18259
18656
|
const artifactDir = checkpointDir(stateDir, checkpointId);
|
|
18260
18657
|
try {
|
|
18261
18658
|
const manifest = JSON.parse(
|
|
18262
|
-
await
|
|
18659
|
+
await readFile10(path52.join(artifactDir, "manifest.json"), "utf8")
|
|
18263
18660
|
);
|
|
18264
18661
|
if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
|
|
18265
18662
|
return canonicalPath(manifest.repoRoot);
|
|
@@ -18267,7 +18664,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
|
|
|
18267
18664
|
} catch {
|
|
18268
18665
|
}
|
|
18269
18666
|
try {
|
|
18270
|
-
const owner = JSON.parse(await
|
|
18667
|
+
const owner = JSON.parse(await readFile10(path52.join(artifactDir, "owner.json"), "utf8"));
|
|
18271
18668
|
return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
|
|
18272
18669
|
} catch {
|
|
18273
18670
|
return null;
|
|
@@ -18283,14 +18680,14 @@ async function checkpointIdsForRepo(stateDir, repoRoot) {
|
|
|
18283
18680
|
}
|
|
18284
18681
|
async function cleanupOrphanedStaging(stateDir) {
|
|
18285
18682
|
const root = checkpointsRoot(stateDir);
|
|
18286
|
-
if (!
|
|
18683
|
+
if (!existsSync14(root)) return;
|
|
18287
18684
|
const now = Date.now();
|
|
18288
18685
|
for (const entry of await readdir2(root, { withFileTypes: true })) {
|
|
18289
18686
|
if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
|
|
18290
|
-
const stagingPath =
|
|
18687
|
+
const stagingPath = path52.join(root, entry.name);
|
|
18291
18688
|
let stale = false;
|
|
18292
18689
|
try {
|
|
18293
|
-
const owner = JSON.parse(await
|
|
18690
|
+
const owner = JSON.parse(await readFile10(path52.join(stagingPath, "owner.json"), "utf8"));
|
|
18294
18691
|
const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
|
|
18295
18692
|
const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
|
|
18296
18693
|
let alive = false;
|
|
@@ -18330,9 +18727,9 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
|
|
|
18330
18727
|
|
|
18331
18728
|
// src/core/recovery/reconcile.ts
|
|
18332
18729
|
init_fingerprint2();
|
|
18333
|
-
import { existsSync as
|
|
18334
|
-
import { readFile as
|
|
18335
|
-
import
|
|
18730
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
18731
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
18732
|
+
import path53 from "node:path";
|
|
18336
18733
|
async function matchRecoverySide(resourceRoot, entries, side) {
|
|
18337
18734
|
for (const entry of entries) {
|
|
18338
18735
|
const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
|
|
@@ -18346,9 +18743,9 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
|
|
|
18346
18743
|
loaded = await readRecoveryArtifact(stateDir, checkpointId);
|
|
18347
18744
|
} catch {
|
|
18348
18745
|
const artifactDir = checkpointDir(stateDir, checkpointId);
|
|
18349
|
-
if (
|
|
18350
|
-
const manifestPath =
|
|
18351
|
-
const hash =
|
|
18746
|
+
if (existsSync15(artifactDir)) {
|
|
18747
|
+
const manifestPath = path53.join(artifactDir, "manifest.json");
|
|
18748
|
+
const hash = existsSync15(manifestPath) ? hashValue(await readFile11(manifestPath, "utf8")) : "unavailable";
|
|
18352
18749
|
await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
|
|
18353
18750
|
}
|
|
18354
18751
|
return "corrupt";
|
|
@@ -18383,8 +18780,8 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
|
|
|
18383
18780
|
|
|
18384
18781
|
// src/core/recovery/resource-identity.ts
|
|
18385
18782
|
init_fingerprint2();
|
|
18386
|
-
import { lstat as lstat8, readFile as
|
|
18387
|
-
import
|
|
18783
|
+
import { lstat as lstat8, readFile as readFile12, realpath as realpath3 } from "node:fs/promises";
|
|
18784
|
+
import path54 from "node:path";
|
|
18388
18785
|
async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
|
|
18389
18786
|
const resolvedRoot = await realpath3(resourceRoot);
|
|
18390
18787
|
if (resourceKind === "directory") {
|
|
@@ -18392,13 +18789,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
|
|
|
18392
18789
|
if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
|
|
18393
18790
|
return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
|
|
18394
18791
|
}
|
|
18395
|
-
const dotGit =
|
|
18792
|
+
const dotGit = path54.join(resolvedRoot, ".git");
|
|
18396
18793
|
const gitInfo = await lstat8(dotGit);
|
|
18397
18794
|
let gitMetadataPath = dotGit;
|
|
18398
18795
|
if (gitInfo.isFile()) {
|
|
18399
|
-
const marker = (await
|
|
18796
|
+
const marker = (await readFile12(dotGit, "utf8")).trim();
|
|
18400
18797
|
if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
|
|
18401
|
-
gitMetadataPath =
|
|
18798
|
+
gitMetadataPath = path54.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
|
|
18402
18799
|
} else if (!gitInfo.isDirectory()) {
|
|
18403
18800
|
throw new Error("recovery_repo_identity_unavailable");
|
|
18404
18801
|
}
|
|
@@ -18463,7 +18860,7 @@ async function garbageCollect(stateDir, config, repoRoot) {
|
|
|
18463
18860
|
async function prepareRecoveryCheckpoint(params) {
|
|
18464
18861
|
const backend = params.backend ?? "git_worktree";
|
|
18465
18862
|
const resourceKind = await resolveRecoveryResourceKind(backend, params.repoRoot);
|
|
18466
|
-
await
|
|
18863
|
+
await mkdir12(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
|
|
18467
18864
|
await cleanupOrphanedStaging(params.stateDir);
|
|
18468
18865
|
await garbageCollect(params.stateDir, params.config, params.repoRoot);
|
|
18469
18866
|
const existing = await checkpointIdsForRepo(params.stateDir, params.repoRoot);
|
|
@@ -18471,16 +18868,16 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18471
18868
|
throw new Error(RECOVERY_CHECKPOINT_QUOTA);
|
|
18472
18869
|
}
|
|
18473
18870
|
const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
|
|
18474
|
-
const temporary =
|
|
18871
|
+
const temporary = path55.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
|
|
18475
18872
|
const finalDir = checkpointDir(params.stateDir, checkpointId);
|
|
18476
|
-
await
|
|
18477
|
-
await atomicWriteJson(
|
|
18873
|
+
await mkdir12(temporary, { recursive: true, mode: 448 });
|
|
18874
|
+
await atomicWriteJson(path55.join(temporary, "owner.json"), {
|
|
18478
18875
|
version: 1,
|
|
18479
18876
|
pid: process.pid,
|
|
18480
18877
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18481
18878
|
repoRoot: canonicalPath(params.repoRoot)
|
|
18482
18879
|
});
|
|
18483
|
-
await
|
|
18880
|
+
await mkdir12(path55.join(temporary, "blobs"), { recursive: true, mode: 448 });
|
|
18484
18881
|
try {
|
|
18485
18882
|
const entries = [];
|
|
18486
18883
|
const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
|
|
@@ -18489,8 +18886,8 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18489
18886
|
)) {
|
|
18490
18887
|
const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
|
|
18491
18888
|
if (protectedRoots.some((root) => {
|
|
18492
|
-
const relative =
|
|
18493
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
18889
|
+
const relative = path55.relative(root, target);
|
|
18890
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path55.sep}`) && !path55.isAbsolute(relative);
|
|
18494
18891
|
})) {
|
|
18495
18892
|
throw new Error("recovery_protected_path");
|
|
18496
18893
|
}
|
|
@@ -18502,7 +18899,7 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18502
18899
|
entries.push({
|
|
18503
18900
|
path: change.relativePath,
|
|
18504
18901
|
before: await captureRecoverySnapshot(baseline, {
|
|
18505
|
-
blobDir:
|
|
18902
|
+
blobDir: path55.join(temporary, "blobs")
|
|
18506
18903
|
}),
|
|
18507
18904
|
after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
|
|
18508
18905
|
});
|
|
@@ -18539,12 +18936,12 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18539
18936
|
entries
|
|
18540
18937
|
};
|
|
18541
18938
|
const manifestHash = hashValue(canonicalStringify(manifest));
|
|
18542
|
-
await atomicWriteJson(
|
|
18939
|
+
await atomicWriteJson(path55.join(temporary, "manifest.json"), manifest);
|
|
18543
18940
|
await writeRecoveryState(temporary, "prepared", manifestHash);
|
|
18544
18941
|
await fsyncPath2(temporary);
|
|
18545
18942
|
const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
|
|
18546
18943
|
if (projectedBytes > params.config.maxBytes) throw new Error(RECOVERY_CHECKPOINT_QUOTA);
|
|
18547
|
-
await
|
|
18944
|
+
await rename4(temporary, finalDir);
|
|
18548
18945
|
await fsyncPath2(checkpointsRoot(params.stateDir));
|
|
18549
18946
|
return {
|
|
18550
18947
|
checkpointId,
|
|
@@ -18582,7 +18979,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18582
18979
|
} catch {
|
|
18583
18980
|
try {
|
|
18584
18981
|
const raw = JSON.parse(
|
|
18585
|
-
await
|
|
18982
|
+
await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
|
|
18586
18983
|
);
|
|
18587
18984
|
rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
|
|
18588
18985
|
} catch {
|
|
@@ -18616,7 +19013,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18616
19013
|
} catch {
|
|
18617
19014
|
try {
|
|
18618
19015
|
const manifest = JSON.parse(
|
|
18619
|
-
await
|
|
19016
|
+
await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
|
|
18620
19017
|
);
|
|
18621
19018
|
if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
|
|
18622
19019
|
if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
|
|
@@ -18641,12 +19038,12 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18641
19038
|
async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
|
|
18642
19039
|
const root = checkpointsRoot(stateDir);
|
|
18643
19040
|
if (!repoRoot) return directorySize(root);
|
|
18644
|
-
if (!
|
|
19041
|
+
if (!existsSync16(root)) return 0;
|
|
18645
19042
|
const expected = canonicalPath(repoRoot);
|
|
18646
19043
|
let total = 0;
|
|
18647
19044
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
18648
19045
|
if (!entry.isDirectory()) continue;
|
|
18649
|
-
const entryPath =
|
|
19046
|
+
const entryPath = path55.join(root, entry.name);
|
|
18650
19047
|
if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
|
|
18651
19048
|
if (await artifactRepoRoot(stateDir, entry.name) === expected) {
|
|
18652
19049
|
total += await directorySize(entryPath);
|
|
@@ -18655,7 +19052,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
|
|
|
18655
19052
|
}
|
|
18656
19053
|
if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
|
|
18657
19054
|
try {
|
|
18658
|
-
const owner = JSON.parse(await
|
|
19055
|
+
const owner = JSON.parse(await readFile13(path55.join(entryPath, "owner.json"), "utf8"));
|
|
18659
19056
|
if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
|
|
18660
19057
|
total += await directorySize(entryPath);
|
|
18661
19058
|
}
|
|
@@ -18697,16 +19094,16 @@ function recoveryFailClosedResult(predicted, reason, signals = []) {
|
|
|
18697
19094
|
init_scrub();
|
|
18698
19095
|
|
|
18699
19096
|
// src/core/transactional/file-checkpoint-backend.ts
|
|
18700
|
-
import { cp, lstat as lstat11, mkdir as
|
|
19097
|
+
import { cp, lstat as lstat11, mkdir as mkdir14, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
|
|
18701
19098
|
import os5 from "node:os";
|
|
18702
|
-
import
|
|
19099
|
+
import path59 from "node:path";
|
|
18703
19100
|
|
|
18704
19101
|
// src/core/transactional/file-checkpoint-git.ts
|
|
18705
19102
|
init_path_utils();
|
|
18706
19103
|
import { spawn as spawn8 } from "node:child_process";
|
|
18707
19104
|
import { createHash as createHash13 } from "node:crypto";
|
|
18708
|
-
import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as
|
|
18709
|
-
import
|
|
19105
|
+
import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile14 } from "node:fs/promises";
|
|
19106
|
+
import path56 from "node:path";
|
|
18710
19107
|
var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
|
|
18711
19108
|
var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
|
|
18712
19109
|
var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
|
|
@@ -18740,7 +19137,7 @@ function rethrowStableFileCheckpointError(error) {
|
|
|
18740
19137
|
}
|
|
18741
19138
|
async function rootGitMetadataPresent(repoRoot) {
|
|
18742
19139
|
try {
|
|
18743
|
-
await lstat9(
|
|
19140
|
+
await lstat9(path56.join(repoRoot, ".git"));
|
|
18744
19141
|
return true;
|
|
18745
19142
|
} catch {
|
|
18746
19143
|
return false;
|
|
@@ -18789,10 +19186,10 @@ function execGit2(repoRoot, args) {
|
|
|
18789
19186
|
}
|
|
18790
19187
|
async function resolveGitPath(repoRoot, gitPath) {
|
|
18791
19188
|
const trimmed = gitPath.trim();
|
|
18792
|
-
if (
|
|
19189
|
+
if (path56.isAbsolute(trimmed)) {
|
|
18793
19190
|
return trimmed;
|
|
18794
19191
|
}
|
|
18795
|
-
return
|
|
19192
|
+
return path56.join(repoRoot, trimmed);
|
|
18796
19193
|
}
|
|
18797
19194
|
async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
|
|
18798
19195
|
await execGit2(sourceRoot, [
|
|
@@ -18834,7 +19231,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
|
|
|
18834
19231
|
destinationRoot,
|
|
18835
19232
|
await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
|
|
18836
19233
|
);
|
|
18837
|
-
const destinationShared =
|
|
19234
|
+
const destinationShared = path56.join(destinationGitDir, path56.basename(sourceShared));
|
|
18838
19235
|
try {
|
|
18839
19236
|
await copyFile3(sourceShared, destinationShared);
|
|
18840
19237
|
} catch (error) {
|
|
@@ -18843,14 +19240,14 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
|
|
|
18843
19240
|
}
|
|
18844
19241
|
async function readGitFile(gitDir, relativePath) {
|
|
18845
19242
|
try {
|
|
18846
|
-
return await
|
|
19243
|
+
return await readFile14(path56.join(gitDir, relativePath));
|
|
18847
19244
|
} catch {
|
|
18848
19245
|
return null;
|
|
18849
19246
|
}
|
|
18850
19247
|
}
|
|
18851
19248
|
async function readAbsoluteGitFile(absolutePath) {
|
|
18852
19249
|
try {
|
|
18853
|
-
return await
|
|
19250
|
+
return await readFile14(absolutePath);
|
|
18854
19251
|
} catch {
|
|
18855
19252
|
return null;
|
|
18856
19253
|
}
|
|
@@ -18867,8 +19264,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
|
|
|
18867
19264
|
repoRoot,
|
|
18868
19265
|
await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
|
|
18869
19266
|
);
|
|
18870
|
-
const relative =
|
|
18871
|
-
const content = typeof relative === "string" && !relative.startsWith("..") && !
|
|
19267
|
+
const relative = path56.resolve(resolved).startsWith(path56.resolve(gitDir)) ? path56.relative(gitDir, resolved) : resolved;
|
|
19268
|
+
const content = typeof relative === "string" && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
|
|
18872
19269
|
if (content !== null) {
|
|
18873
19270
|
hashGitFileContent(hash, gitPath, content);
|
|
18874
19271
|
}
|
|
@@ -18878,13 +19275,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
|
|
|
18878
19275
|
async function hashGitTree(gitDir, relativeDir, hash) {
|
|
18879
19276
|
let names;
|
|
18880
19277
|
try {
|
|
18881
|
-
names = await readdir4(
|
|
19278
|
+
names = await readdir4(path56.join(gitDir, relativeDir));
|
|
18882
19279
|
} catch {
|
|
18883
19280
|
return;
|
|
18884
19281
|
}
|
|
18885
19282
|
for (const name of names.sort()) {
|
|
18886
|
-
const relativePath = relativeDir ?
|
|
18887
|
-
const absolutePath =
|
|
19283
|
+
const relativePath = relativeDir ? path56.join(relativeDir, name) : name;
|
|
19284
|
+
const absolutePath = path56.join(gitDir, relativePath);
|
|
18888
19285
|
let childNames = null;
|
|
18889
19286
|
try {
|
|
18890
19287
|
childNames = await readdir4(absolutePath);
|
|
@@ -18906,7 +19303,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
|
|
|
18906
19303
|
}
|
|
18907
19304
|
async function computeGitMetadataFingerprint(repoRoot) {
|
|
18908
19305
|
const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
|
|
18909
|
-
const gitDir =
|
|
19306
|
+
const gitDir = path56.isAbsolute(gitDirRel) ? gitDirRel : path56.join(repoRoot, gitDirRel);
|
|
18910
19307
|
const hash = createHash13("sha256");
|
|
18911
19308
|
for (const file of [
|
|
18912
19309
|
"HEAD",
|
|
@@ -18929,8 +19326,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18929
19326
|
const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
|
|
18930
19327
|
if (sharedIndex) {
|
|
18931
19328
|
const resolved = await resolveGitPath(repoRoot, sharedIndex);
|
|
18932
|
-
const relative =
|
|
18933
|
-
const content = relative && !relative.startsWith("..") && !
|
|
19329
|
+
const relative = path56.relative(gitDir, resolved);
|
|
19330
|
+
const content = relative && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
|
|
18934
19331
|
if (content !== null) {
|
|
18935
19332
|
hashGitFileContent(hash, "shared-index", content);
|
|
18936
19333
|
}
|
|
@@ -18941,7 +19338,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18941
19338
|
await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
|
|
18942
19339
|
}
|
|
18943
19340
|
try {
|
|
18944
|
-
const rootGitPath =
|
|
19341
|
+
const rootGitPath = path56.join(repoRoot, ".git");
|
|
18945
19342
|
const rootGitInfo = await lstat9(rootGitPath);
|
|
18946
19343
|
if (rootGitInfo.isFile()) {
|
|
18947
19344
|
const content = await readAbsoluteGitFile(rootGitPath);
|
|
@@ -18957,14 +19354,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18957
19354
|
function resolveExecutionCwdRelative(resourceRoot, cwd) {
|
|
18958
19355
|
const resolvedCwd = canonicalPath(cwd);
|
|
18959
19356
|
const resourceCanonical = canonicalPath(resourceRoot);
|
|
18960
|
-
const relative =
|
|
19357
|
+
const relative = path56.relative(resourceCanonical, resolvedCwd);
|
|
18961
19358
|
if (relative === "" || relative === ".") {
|
|
18962
19359
|
return "";
|
|
18963
19360
|
}
|
|
18964
|
-
if (relative.startsWith("..") ||
|
|
19361
|
+
if (relative.startsWith("..") || path56.isAbsolute(relative)) {
|
|
18965
19362
|
throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
|
|
18966
19363
|
}
|
|
18967
|
-
return relative.split(
|
|
19364
|
+
return relative.split(path56.sep).join("/");
|
|
18968
19365
|
}
|
|
18969
19366
|
|
|
18970
19367
|
// src/core/transactional/file-checkpoint-isolation.ts
|
|
@@ -18985,8 +19382,8 @@ function fileCheckpointIsolationReason(context) {
|
|
|
18985
19382
|
}
|
|
18986
19383
|
|
|
18987
19384
|
// src/core/transactional/file-checkpoint-staging.ts
|
|
18988
|
-
import { readdir as readdir5, readFile as
|
|
18989
|
-
import
|
|
19385
|
+
import { readdir as readdir5, readFile as readFile15, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
|
|
19386
|
+
import path57 from "node:path";
|
|
18990
19387
|
function isOwnerProcessAlive(pid) {
|
|
18991
19388
|
try {
|
|
18992
19389
|
process.kill(pid, 0);
|
|
@@ -18996,12 +19393,12 @@ function isOwnerProcessAlive(pid) {
|
|
|
18996
19393
|
}
|
|
18997
19394
|
}
|
|
18998
19395
|
async function writeOwnerMarker(stagingRoot, marker) {
|
|
18999
|
-
await writeFile8(
|
|
19396
|
+
await writeFile8(path57.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
|
|
19000
19397
|
`, "utf8");
|
|
19001
19398
|
}
|
|
19002
19399
|
async function readOwnerMarker(stagingRoot) {
|
|
19003
19400
|
try {
|
|
19004
|
-
const raw = await
|
|
19401
|
+
const raw = await readFile15(path57.join(stagingRoot, "owner.json"), "utf8");
|
|
19005
19402
|
return JSON.parse(raw.trim());
|
|
19006
19403
|
} catch {
|
|
19007
19404
|
return null;
|
|
@@ -19019,7 +19416,7 @@ async function collectDeadOwnerStaging(parentDir) {
|
|
|
19019
19416
|
if (!name.startsWith("belay-file-checkpoint-")) {
|
|
19020
19417
|
continue;
|
|
19021
19418
|
}
|
|
19022
|
-
const stagingRoot =
|
|
19419
|
+
const stagingRoot = path57.join(parentDir, name);
|
|
19023
19420
|
const marker = await readOwnerMarker(stagingRoot);
|
|
19024
19421
|
if (!marker) {
|
|
19025
19422
|
dead.push(stagingRoot);
|
|
@@ -19042,7 +19439,7 @@ import { constants as fsConstants2 } from "node:fs";
|
|
|
19042
19439
|
import {
|
|
19043
19440
|
copyFile as copyFile4,
|
|
19044
19441
|
lstat as lstat10,
|
|
19045
|
-
mkdir as
|
|
19442
|
+
mkdir as mkdir13,
|
|
19046
19443
|
mkdtemp as mkdtemp4,
|
|
19047
19444
|
readlink as readlink5,
|
|
19048
19445
|
rm as rm8,
|
|
@@ -19051,17 +19448,17 @@ import {
|
|
|
19051
19448
|
writeFile as writeFile9
|
|
19052
19449
|
} from "node:fs/promises";
|
|
19053
19450
|
import os4 from "node:os";
|
|
19054
|
-
import
|
|
19451
|
+
import path58 from "node:path";
|
|
19055
19452
|
var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
|
|
19056
19453
|
async function chmodSafe2(target, mode) {
|
|
19057
19454
|
try {
|
|
19058
|
-
const { chmod:
|
|
19059
|
-
await
|
|
19455
|
+
const { chmod: chmod5 } = await import("node:fs/promises");
|
|
19456
|
+
await chmod5(target, mode & 511);
|
|
19060
19457
|
} catch {
|
|
19061
19458
|
}
|
|
19062
19459
|
}
|
|
19063
19460
|
async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
|
|
19064
|
-
await
|
|
19461
|
+
await mkdir13(path58.dirname(destinationPath), { recursive: true });
|
|
19065
19462
|
if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
|
|
19066
19463
|
try {
|
|
19067
19464
|
await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
|
|
@@ -19083,11 +19480,11 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
|
|
|
19083
19480
|
const info = await lstat10(sourcePath);
|
|
19084
19481
|
await rm8(destinationPath, { force: true, recursive: false });
|
|
19085
19482
|
if (info.isDirectory() && !info.isSymbolicLink()) {
|
|
19086
|
-
await
|
|
19483
|
+
await mkdir13(destinationPath, { recursive: true, mode: info.mode & 511 });
|
|
19087
19484
|
return strategy;
|
|
19088
19485
|
}
|
|
19089
19486
|
if (info.isSymbolicLink()) {
|
|
19090
|
-
await
|
|
19487
|
+
await mkdir13(path58.dirname(destinationPath), { recursive: true });
|
|
19091
19488
|
await symlink4(await readlink5(sourcePath), destinationPath);
|
|
19092
19489
|
return strategy;
|
|
19093
19490
|
}
|
|
@@ -19143,9 +19540,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
|
|
|
19143
19540
|
async function probeFileCloneStrategy() {
|
|
19144
19541
|
let tempDir = null;
|
|
19145
19542
|
try {
|
|
19146
|
-
tempDir = await mkdtemp4(
|
|
19147
|
-
const source =
|
|
19148
|
-
const destination =
|
|
19543
|
+
tempDir = await mkdtemp4(path58.join(os4.tmpdir(), "belay-clone-probe-"));
|
|
19544
|
+
const source = path58.join(tempDir, "source.txt");
|
|
19545
|
+
const destination = path58.join(tempDir, "dest.txt");
|
|
19149
19546
|
await writeFile9(source, "probe\n");
|
|
19150
19547
|
if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
|
|
19151
19548
|
try {
|
|
@@ -19298,11 +19695,11 @@ async function protectedRootState(root) {
|
|
|
19298
19695
|
return `directory:${node.hash}:${index.treeHash}`;
|
|
19299
19696
|
}
|
|
19300
19697
|
function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
|
|
19301
|
-
const relative =
|
|
19302
|
-
if (relative === "" || relative.startsWith("..") ||
|
|
19698
|
+
const relative = path59.relative(path59.resolve(resourceRoot), path59.resolve(protectedRoot));
|
|
19699
|
+
if (relative === "" || relative.startsWith("..") || path59.isAbsolute(relative)) {
|
|
19303
19700
|
return null;
|
|
19304
19701
|
}
|
|
19305
|
-
return
|
|
19702
|
+
return path59.join(executionRoot, relative);
|
|
19306
19703
|
}
|
|
19307
19704
|
async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
|
|
19308
19705
|
const states = /* @__PURE__ */ new Map();
|
|
@@ -19327,15 +19724,15 @@ async function directoryByteSize(root, deadlineMs) {
|
|
|
19327
19724
|
}
|
|
19328
19725
|
let total = 0;
|
|
19329
19726
|
for (const name of await readdir6(root)) {
|
|
19330
|
-
total += await directoryByteSize(
|
|
19727
|
+
total += await directoryByteSize(path59.join(root, name), deadlineMs);
|
|
19331
19728
|
}
|
|
19332
19729
|
return total;
|
|
19333
19730
|
}
|
|
19334
19731
|
async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
|
|
19335
19732
|
const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
|
|
19336
|
-
const sourceGitDir =
|
|
19337
|
-
const relativeGitDir =
|
|
19338
|
-
const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ?
|
|
19733
|
+
const sourceGitDir = path59.isAbsolute(gitDirRel) ? gitDirRel : path59.join(sourceRoot, gitDirRel);
|
|
19734
|
+
const relativeGitDir = path59.relative(path59.resolve(sourceRoot), path59.resolve(sourceGitDir));
|
|
19735
|
+
const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path59.join(destinationRoot, relativeGitDir) : path59.join(destinationRoot, ".git");
|
|
19339
19736
|
await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
|
|
19340
19737
|
}
|
|
19341
19738
|
async function prepareDirtyGitSnapshot(context) {
|
|
@@ -19344,7 +19741,7 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19344
19741
|
const quotas = context.fileCheckpoint;
|
|
19345
19742
|
const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
|
|
19346
19743
|
await removeDeadOwnerStaging(os5.tmpdir());
|
|
19347
|
-
const stagingRoot = await mkdtemp5(
|
|
19744
|
+
const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
|
|
19348
19745
|
await writeOwnerMarker(stagingRoot, {
|
|
19349
19746
|
version: 1,
|
|
19350
19747
|
pid: process.pid,
|
|
@@ -19352,8 +19749,8 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19352
19749
|
resourceRoot: context.repoRoot,
|
|
19353
19750
|
backend: "file_checkpoint"
|
|
19354
19751
|
});
|
|
19355
|
-
const baselineRoot =
|
|
19356
|
-
const executionRoot =
|
|
19752
|
+
const baselineRoot = path59.join(stagingRoot, "baseline");
|
|
19753
|
+
const executionRoot = path59.join(stagingRoot, "execution");
|
|
19357
19754
|
try {
|
|
19358
19755
|
resolveExecutionCwdRelative(context.repoRoot, context.cwd);
|
|
19359
19756
|
const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
|
|
@@ -19387,7 +19784,7 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19387
19784
|
throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
|
|
19388
19785
|
}
|
|
19389
19786
|
await writeFile10(
|
|
19390
|
-
|
|
19787
|
+
path59.join(stagingRoot, "baseline-index.json"),
|
|
19391
19788
|
`${JSON.stringify(baselineIndex)}
|
|
19392
19789
|
`,
|
|
19393
19790
|
"utf8"
|
|
@@ -19437,7 +19834,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19437
19834
|
const quotas = context.fileCheckpoint;
|
|
19438
19835
|
const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
|
|
19439
19836
|
await removeDeadOwnerStaging(os5.tmpdir());
|
|
19440
|
-
const stagingRoot = await mkdtemp5(
|
|
19837
|
+
const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
|
|
19441
19838
|
await writeOwnerMarker(stagingRoot, {
|
|
19442
19839
|
version: 1,
|
|
19443
19840
|
pid: process.pid,
|
|
@@ -19445,8 +19842,8 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19445
19842
|
resourceRoot: context.repoRoot,
|
|
19446
19843
|
backend: "file_checkpoint"
|
|
19447
19844
|
});
|
|
19448
|
-
const baselineRoot =
|
|
19449
|
-
const executionRoot =
|
|
19845
|
+
const baselineRoot = path59.join(stagingRoot, "baseline");
|
|
19846
|
+
const executionRoot = path59.join(stagingRoot, "execution");
|
|
19450
19847
|
try {
|
|
19451
19848
|
resolveExecutionCwdRelative(context.repoRoot, context.cwd);
|
|
19452
19849
|
const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
|
|
@@ -19455,7 +19852,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19455
19852
|
quotas,
|
|
19456
19853
|
deadlineMs
|
|
19457
19854
|
});
|
|
19458
|
-
await
|
|
19855
|
+
await mkdir14(baselineRoot, { recursive: true });
|
|
19459
19856
|
const baselineIndex = await buildFileTreeIndex({
|
|
19460
19857
|
resourceRoot: baselineRoot,
|
|
19461
19858
|
excludedRoots,
|
|
@@ -19475,7 +19872,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19475
19872
|
throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
|
|
19476
19873
|
}
|
|
19477
19874
|
await writeFile10(
|
|
19478
|
-
|
|
19875
|
+
path59.join(stagingRoot, "baseline-index.json"),
|
|
19479
19876
|
`${JSON.stringify(baselineIndex)}
|
|
19480
19877
|
`,
|
|
19481
19878
|
"utf8"
|
|
@@ -19485,7 +19882,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19485
19882
|
quotas,
|
|
19486
19883
|
deadlineMs
|
|
19487
19884
|
});
|
|
19488
|
-
await
|
|
19885
|
+
await mkdir14(executionRoot, { recursive: true });
|
|
19489
19886
|
const finalSourceIndex = await buildFileTreeIndex({
|
|
19490
19887
|
resourceRoot: context.repoRoot,
|
|
19491
19888
|
excludedRoots,
|
|
@@ -19828,10 +20225,10 @@ async function selectTransactionalBackend(context) {
|
|
|
19828
20225
|
}
|
|
19829
20226
|
|
|
19830
20227
|
// src/core/transactional/diff-evaluator.ts
|
|
19831
|
-
import
|
|
20228
|
+
import path60 from "node:path";
|
|
19832
20229
|
init_path_utils();
|
|
19833
20230
|
function categorizeChange(change, ctx) {
|
|
19834
|
-
const absolutePath = canonicalPath(
|
|
20231
|
+
const absolutePath = canonicalPath(path60.join(ctx.repoRoot, change.relativePath));
|
|
19835
20232
|
if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
|
|
19836
20233
|
return "repo_outside";
|
|
19837
20234
|
}
|
|
@@ -20381,17 +20778,55 @@ function formatJudgeInfrastructureDenyMessage(params) {
|
|
|
20381
20778
|
}
|
|
20382
20779
|
|
|
20383
20780
|
// src/core/notify.ts
|
|
20781
|
+
init_path_utils();
|
|
20384
20782
|
import { execFile } from "node:child_process";
|
|
20783
|
+
import path61 from "node:path";
|
|
20385
20784
|
import { promisify } from "node:util";
|
|
20386
20785
|
var execFileAsync = promisify(execFile);
|
|
20387
|
-
|
|
20388
|
-
|
|
20389
|
-
|
|
20786
|
+
var LOOPBACK_WEBHOOK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
20787
|
+
function webhookConfigIssue(url) {
|
|
20788
|
+
let parsed;
|
|
20789
|
+
try {
|
|
20790
|
+
parsed = new URL(url);
|
|
20791
|
+
} catch {
|
|
20792
|
+
return `notifications.webhookUrl is invalid: ${url}`;
|
|
20793
|
+
}
|
|
20794
|
+
if (parsed.protocol === "https:") {
|
|
20795
|
+
return null;
|
|
20796
|
+
}
|
|
20797
|
+
const normalizedHostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
20798
|
+
if (parsed.protocol === "http:" && LOOPBACK_WEBHOOK_HOSTS.has(normalizedHostname)) {
|
|
20799
|
+
return null;
|
|
20800
|
+
}
|
|
20801
|
+
return `notifications.webhookUrl must use https (http is allowed only for localhost, 127.0.0.1, or ::1): ${url}`;
|
|
20802
|
+
}
|
|
20803
|
+
function commandHookConfigIssue(commandHook, repoRoot) {
|
|
20804
|
+
if (!path61.isAbsolute(commandHook)) {
|
|
20805
|
+
return `notifications.commandHook must be an absolute path: ${commandHook}`;
|
|
20806
|
+
}
|
|
20807
|
+
if (pathWithinRoot(canonicalPath(repoRoot), canonicalPath(commandHook))) {
|
|
20808
|
+
return `notifications.commandHook must not be inside the repository: ${commandHook}`;
|
|
20809
|
+
}
|
|
20810
|
+
return null;
|
|
20811
|
+
}
|
|
20812
|
+
async function notifyDeny(config, event, deps = {
|
|
20813
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
20814
|
+
execFile: (file, args, options) => execFileAsync(file, [...args], options)
|
|
20815
|
+
}) {
|
|
20816
|
+
const payload = JSON.stringify({
|
|
20817
|
+
approvalId: event.approvalId,
|
|
20818
|
+
reason: event.reason,
|
|
20819
|
+
summary: event.summary,
|
|
20820
|
+
repoRoot: event.repoRoot,
|
|
20821
|
+
fingerprint: event.fingerprint
|
|
20822
|
+
});
|
|
20823
|
+
const webhookIssue = config.webhookUrl ? webhookConfigIssue(config.webhookUrl) : null;
|
|
20824
|
+
if (config.webhookUrl && !webhookIssue) {
|
|
20390
20825
|
try {
|
|
20391
20826
|
const controller = new AbortController();
|
|
20392
20827
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
20393
20828
|
try {
|
|
20394
|
-
await fetch(config.webhookUrl, {
|
|
20829
|
+
await deps.fetch(config.webhookUrl, {
|
|
20395
20830
|
method: "POST",
|
|
20396
20831
|
headers: { "content-type": "application/json" },
|
|
20397
20832
|
body: payload,
|
|
@@ -20403,17 +20838,16 @@ async function notifyDeny(config, event) {
|
|
|
20403
20838
|
} catch {
|
|
20404
20839
|
}
|
|
20405
20840
|
}
|
|
20406
|
-
|
|
20841
|
+
const commandHookIssue = config.commandHook ? commandHookConfigIssue(config.commandHook, event.repoRoot) : null;
|
|
20842
|
+
if (config.commandHook && !commandHookIssue) {
|
|
20407
20843
|
try {
|
|
20408
|
-
await
|
|
20844
|
+
await deps.execFile(config.commandHook, [], {
|
|
20409
20845
|
env: {
|
|
20410
|
-
...process.env,
|
|
20411
20846
|
BELAY_APPROVAL_ID: event.approvalId,
|
|
20412
20847
|
BELAY_REASON: event.reason,
|
|
20413
20848
|
BELAY_SUMMARY: event.summary,
|
|
20414
20849
|
BELAY_REPO_ROOT: event.repoRoot,
|
|
20415
|
-
BELAY_FINGERPRINT: event.fingerprint
|
|
20416
|
-
BELAY_APPROVAL_TOKEN: event.approvalToken ?? ""
|
|
20850
|
+
BELAY_FINGERPRINT: event.fingerprint
|
|
20417
20851
|
}
|
|
20418
20852
|
});
|
|
20419
20853
|
} catch {
|
|
@@ -20423,9 +20857,10 @@ async function notifyDeny(config, event) {
|
|
|
20423
20857
|
|
|
20424
20858
|
// src/adapters/shared/gate-runtime.ts
|
|
20425
20859
|
init_path_utils();
|
|
20860
|
+
init_repo_config_trust();
|
|
20426
20861
|
|
|
20427
20862
|
// src/adapters/layouts/protected-paths.ts
|
|
20428
|
-
import
|
|
20863
|
+
import path62 from "node:path";
|
|
20429
20864
|
function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
|
|
20430
20865
|
const roots = [
|
|
20431
20866
|
layout.configPath(repoRoot),
|
|
@@ -20437,7 +20872,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
|
|
|
20437
20872
|
if (controlPlaneDir) {
|
|
20438
20873
|
roots.push(controlPlaneDir);
|
|
20439
20874
|
}
|
|
20440
|
-
return roots.map((entry) =>
|
|
20875
|
+
return roots.map((entry) => path62.resolve(entry));
|
|
20441
20876
|
}
|
|
20442
20877
|
|
|
20443
20878
|
// src/adapters/shared/gate-runtime.ts
|
|
@@ -20478,7 +20913,7 @@ async function appendReplayAuditSafely(ctx, deps, event) {
|
|
|
20478
20913
|
}
|
|
20479
20914
|
async function loadJsonFile(filePath, fallback) {
|
|
20480
20915
|
try {
|
|
20481
|
-
const raw = await
|
|
20916
|
+
const raw = await readFile16(filePath, "utf8");
|
|
20482
20917
|
return JSON.parse(raw);
|
|
20483
20918
|
} catch {
|
|
20484
20919
|
return fallback;
|
|
@@ -20487,11 +20922,18 @@ async function loadJsonFile(filePath, fallback) {
|
|
|
20487
20922
|
function createDefaultGateRuntimeDeps() {
|
|
20488
20923
|
return {
|
|
20489
20924
|
async readConfig(configPath) {
|
|
20490
|
-
|
|
20925
|
+
try {
|
|
20926
|
+
return JSON.parse(await readFile16(configPath, "utf8"));
|
|
20927
|
+
} catch (error) {
|
|
20928
|
+
if (error.code === "ENOENT") {
|
|
20929
|
+
return {};
|
|
20930
|
+
}
|
|
20931
|
+
throw error;
|
|
20932
|
+
}
|
|
20491
20933
|
},
|
|
20492
20934
|
async appendAudit(ctx, event) {
|
|
20493
|
-
const auditPath =
|
|
20494
|
-
await
|
|
20935
|
+
const auditPath = path63.join(ctx.repoRoot, ctx.config.audit.logPath);
|
|
20936
|
+
await mkdir15(path63.dirname(auditPath), { recursive: true });
|
|
20495
20937
|
const provenance = auditProvenance(ctx.config);
|
|
20496
20938
|
const record = {
|
|
20497
20939
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -20524,7 +20966,7 @@ function createDefaultGateRuntimeDeps() {
|
|
|
20524
20966
|
};
|
|
20525
20967
|
},
|
|
20526
20968
|
async writeApprovals(filePath, state) {
|
|
20527
|
-
await
|
|
20969
|
+
await mkdir15(path63.dirname(filePath), { recursive: true });
|
|
20528
20970
|
await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
|
|
20529
20971
|
`, "utf8");
|
|
20530
20972
|
},
|
|
@@ -20545,10 +20987,11 @@ function createDefaultGateRuntimeDeps() {
|
|
|
20545
20987
|
}
|
|
20546
20988
|
async function resolveGateConfig(ctx, deps) {
|
|
20547
20989
|
const loaded = await deps.readConfig(ctx.configPath);
|
|
20990
|
+
await assertRepoConfigTrusted(ctx.repoRoot, ctx.layout.name, loaded);
|
|
20548
20991
|
let teamConfig = null;
|
|
20549
20992
|
const teamPath = teamConfigPath();
|
|
20550
|
-
if (
|
|
20551
|
-
teamConfig = JSON.parse(await
|
|
20993
|
+
if (existsSync17(teamPath)) {
|
|
20994
|
+
teamConfig = JSON.parse(await readFile16(teamPath, "utf8"));
|
|
20552
20995
|
}
|
|
20553
20996
|
return resolveLayeredConfig({
|
|
20554
20997
|
repoConfig: loaded,
|
|
@@ -20686,7 +21129,7 @@ function deriveWorkspaceRootScopeHint(params) {
|
|
|
20686
21129
|
if (!targetPath) {
|
|
20687
21130
|
return void 0;
|
|
20688
21131
|
}
|
|
20689
|
-
const candidateRoot = canonicalPath(
|
|
21132
|
+
const candidateRoot = canonicalPath(path63.dirname(targetPath));
|
|
20690
21133
|
const validation = validateTrustedWorkspaceRootCandidate({
|
|
20691
21134
|
candidatePath: candidateRoot,
|
|
20692
21135
|
repoRoot: action.repoRoot,
|
|
@@ -21262,7 +21705,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21262
21705
|
event: auditEvent,
|
|
21263
21706
|
sourceEvent,
|
|
21264
21707
|
kind,
|
|
21265
|
-
repoLabel:
|
|
21708
|
+
repoLabel: path63.basename(ctx.repoRoot),
|
|
21266
21709
|
...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
|
|
21267
21710
|
fingerprint: result.fingerprint,
|
|
21268
21711
|
summary: result.normalizedCommand ?? result.summary ?? "",
|
|
@@ -21294,21 +21737,6 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21294
21737
|
if (created) {
|
|
21295
21738
|
await recordGateApprovalAsk(stateDir, result.reason, false);
|
|
21296
21739
|
}
|
|
21297
|
-
let approvalToken;
|
|
21298
|
-
try {
|
|
21299
|
-
approvalToken = await issueApprovalToken(
|
|
21300
|
-
{
|
|
21301
|
-
approvalId: approval.approvalId,
|
|
21302
|
-
fingerprint: approval.fingerprint,
|
|
21303
|
-
repoRoot: approval.repoRoot,
|
|
21304
|
-
issuedAt: approval.createdAt,
|
|
21305
|
-
expiresAt: approval.expiresAt
|
|
21306
|
-
},
|
|
21307
|
-
configuredControlPlaneDir(ctx.config)
|
|
21308
|
-
);
|
|
21309
|
-
} catch {
|
|
21310
|
-
approvalToken = void 0;
|
|
21311
|
-
}
|
|
21312
21740
|
const denialReason = failure?.reason ?? result.reason;
|
|
21313
21741
|
if (ctx.config.notifications.webhookUrl || ctx.config.notifications.commandHook) {
|
|
21314
21742
|
await notifyDeny(ctx.config.notifications, {
|
|
@@ -21316,8 +21744,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21316
21744
|
reason: denialReason,
|
|
21317
21745
|
summary: result.normalizedCommand ?? result.summary ?? "",
|
|
21318
21746
|
repoRoot: ctx.repoRoot,
|
|
21319
|
-
fingerprint: result.fingerprint
|
|
21320
|
-
approvalToken
|
|
21747
|
+
fingerprint: result.fingerprint
|
|
21321
21748
|
});
|
|
21322
21749
|
}
|
|
21323
21750
|
const failureAuditFields = typeof failure?.auditFields === "function" ? failure.auditFields(approval) : failure?.auditFields;
|
|
@@ -21728,38 +22155,38 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
|
|
|
21728
22155
|
}
|
|
21729
22156
|
|
|
21730
22157
|
// src/adapters/shared/repo-root.ts
|
|
21731
|
-
import { existsSync as
|
|
21732
|
-
import
|
|
22158
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
22159
|
+
import path64 from "node:path";
|
|
21733
22160
|
function belayConfigPath(current, adapterName) {
|
|
21734
22161
|
if (adapterName === "cursor") {
|
|
21735
|
-
return
|
|
22162
|
+
return path64.join(current, ".cursor", "belay.config.json");
|
|
21736
22163
|
}
|
|
21737
22164
|
if (adapterName === "claude") {
|
|
21738
|
-
return
|
|
22165
|
+
return path64.join(current, ".claude", "belay.config.json");
|
|
21739
22166
|
}
|
|
21740
|
-
return
|
|
22167
|
+
return path64.join(current, ".codex", "belay.config.json");
|
|
21741
22168
|
}
|
|
21742
22169
|
function markerMatches(current, marker, layout) {
|
|
21743
|
-
const markerPath =
|
|
21744
|
-
if (!
|
|
22170
|
+
const markerPath = path64.join(current, marker);
|
|
22171
|
+
if (!existsSync18(markerPath)) {
|
|
21745
22172
|
return false;
|
|
21746
22173
|
}
|
|
21747
22174
|
if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
|
|
21748
|
-
return
|
|
22175
|
+
return existsSync18(belayConfigPath(current, layout.name));
|
|
21749
22176
|
}
|
|
21750
22177
|
return true;
|
|
21751
22178
|
}
|
|
21752
22179
|
function findRepoRoot(startPath, layout) {
|
|
21753
|
-
let current =
|
|
22180
|
+
let current = path64.resolve(startPath);
|
|
21754
22181
|
while (true) {
|
|
21755
22182
|
for (const marker of layout.repoRootMarkers) {
|
|
21756
22183
|
if (markerMatches(current, marker, layout)) {
|
|
21757
22184
|
return current;
|
|
21758
22185
|
}
|
|
21759
22186
|
}
|
|
21760
|
-
const parent =
|
|
22187
|
+
const parent = path64.dirname(current);
|
|
21761
22188
|
if (parent === current) {
|
|
21762
|
-
return
|
|
22189
|
+
return path64.resolve(startPath);
|
|
21763
22190
|
}
|
|
21764
22191
|
current = parent;
|
|
21765
22192
|
}
|
|
@@ -21957,25 +22384,12 @@ async function runToolGateHook(_eventName) {
|
|
|
21957
22384
|
return;
|
|
21958
22385
|
}
|
|
21959
22386
|
if (!mappedKind) {
|
|
21960
|
-
const verdict3 =
|
|
21961
|
-
toolName,
|
|
21962
|
-
payload,
|
|
21963
|
-
repoRoot: ctx.repoRoot,
|
|
21964
|
-
reason: "unmapped_tool",
|
|
21965
|
-
mode: ctx.config.mode,
|
|
21966
|
-
userMessage: "belay does not recognize this tool action. Run belay doctor, then retry.",
|
|
21967
|
-
agentMessage: "Belay denied this action because the tool could not be normalized."
|
|
21968
|
-
});
|
|
21969
|
-
await deps.appendAudit(ctx, {
|
|
21970
|
-
event: "preToolUse",
|
|
22387
|
+
const verdict3 = await evaluateGatedAction(ctx, deps, {
|
|
21971
22388
|
kind: "tool",
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
permission: "deny",
|
|
21977
|
-
summary: toolName,
|
|
21978
|
-
...effectPlanAuditFields(verdict3.effectPlan)
|
|
22389
|
+
cwd,
|
|
22390
|
+
payload,
|
|
22391
|
+
toolName,
|
|
22392
|
+
sourceEvent: "PreToolUse"
|
|
21979
22393
|
});
|
|
21980
22394
|
jsonResponse(gateVerdictToClaudePreToolUseResponse(verdict3));
|
|
21981
22395
|
return;
|