@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
|
@@ -1504,7 +1504,7 @@ function normalizeConfig(config) {
|
|
|
1504
1504
|
policy: {
|
|
1505
1505
|
unknownLocalEffect: v4.policy?.unknownLocalEffect === "deny" ? "deny" : v4.policy?.unknownLocalEffect === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unknownLocalEffect,
|
|
1506
1506
|
unparseableShell: v4.policy?.unparseableShell === "deny" ? "deny" : v4.policy?.unparseableShell === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unparseableShell,
|
|
1507
|
-
codexUnmappedTool:
|
|
1507
|
+
codexUnmappedTool: "allow",
|
|
1508
1508
|
fenceWarnThreshold: typeof v4.policy?.fenceWarnThreshold === "number" && v4.policy.fenceWarnThreshold > 0 && v4.policy.fenceWarnThreshold <= 1 ? v4.policy.fenceWarnThreshold : DEFAULT_FENCE_WARN_THRESHOLD,
|
|
1509
1509
|
confidenceThresholds: {
|
|
1510
1510
|
allow: typeof v4.policy?.confidenceThresholds?.allow === "number" ? v4.policy.confidenceThresholds.allow : DEFAULT_CONFIDENCE_THRESHOLDS.allow,
|
|
@@ -1806,7 +1806,7 @@ var init_config = __esm({
|
|
|
1806
1806
|
DEFAULT_POLICY_V3 = {
|
|
1807
1807
|
unknownLocalEffect: "allow_flagged",
|
|
1808
1808
|
unparseableShell: "deny",
|
|
1809
|
-
codexUnmappedTool: "
|
|
1809
|
+
codexUnmappedTool: "allow",
|
|
1810
1810
|
confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
|
|
1811
1811
|
modelAssist: { ...DEFAULT_MODEL_ASSIST },
|
|
1812
1812
|
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 existsSync2 } 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 path11 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 path11.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 (!existsSync2(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 existsSync3 } 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 (existsSync3(configPathFor(repoRoot, "claude"))) {
|
|
3527
3640
|
return "claude";
|
|
3528
3641
|
}
|
|
3529
|
-
if (
|
|
3642
|
+
if (existsSync3(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 (existsSync3(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 (existsSync3(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: existsSync3(configPath) ? configPath : void 0
|
|
3558
3671
|
});
|
|
3559
3672
|
}
|
|
3560
3673
|
async function loadConfigFile(repoRoot, adapter = detectAdapterName(repoRoot)) {
|
|
@@ -3569,21 +3682,22 @@ 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
|
|
|
3575
3689
|
// src/adapters/codex/runtime-entry.ts
|
|
3576
3690
|
init_codex();
|
|
3577
|
-
import
|
|
3691
|
+
import path65 from "node:path";
|
|
3578
3692
|
import process2 from "node:process";
|
|
3579
3693
|
|
|
3580
3694
|
// src/adapters/shared/gate-runtime.ts
|
|
3581
3695
|
init_approval();
|
|
3582
3696
|
init_approval_replay();
|
|
3583
3697
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
3584
|
-
import { existsSync as
|
|
3585
|
-
import { mkdir as
|
|
3586
|
-
import
|
|
3698
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
3699
|
+
import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile11 } from "node:fs/promises";
|
|
3700
|
+
import path63 from "node:path";
|
|
3587
3701
|
|
|
3588
3702
|
// src/core/approval-service.ts
|
|
3589
3703
|
init_config_io();
|
|
@@ -3593,47 +3707,35 @@ init_approval_replay();
|
|
|
3593
3707
|
// src/core/approval-token.ts
|
|
3594
3708
|
init_config();
|
|
3595
3709
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
3596
|
-
import { existsSync as
|
|
3597
|
-
import { mkdir as
|
|
3598
|
-
import
|
|
3599
|
-
function base64UrlEncode(value) {
|
|
3600
|
-
return Buffer.from(value, "utf8").toString("base64url");
|
|
3601
|
-
}
|
|
3710
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3711
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
3712
|
+
import path12 from "node:path";
|
|
3602
3713
|
function base64UrlDecode(value) {
|
|
3603
3714
|
return Buffer.from(value, "base64url").toString("utf8");
|
|
3604
3715
|
}
|
|
3605
3716
|
function approvalSigningKeyPath(controlPlaneDir = defaultControlPlaneDir()) {
|
|
3606
|
-
return
|
|
3717
|
+
return path12.join(controlPlaneDir, "approval-signing.key");
|
|
3607
3718
|
}
|
|
3608
3719
|
async function loadOrCreateApprovalSigningKey(controlPlaneDir = defaultControlPlaneDir()) {
|
|
3609
3720
|
const keyPath = approvalSigningKeyPath(controlPlaneDir);
|
|
3610
|
-
if (
|
|
3611
|
-
return
|
|
3721
|
+
if (existsSync4(keyPath)) {
|
|
3722
|
+
return readFile4(keyPath);
|
|
3612
3723
|
}
|
|
3613
|
-
await
|
|
3724
|
+
await mkdir4(controlPlaneDir, { recursive: true });
|
|
3614
3725
|
const key = randomBytes(32);
|
|
3615
3726
|
await writeFile2(keyPath, key, { mode: 384 });
|
|
3616
3727
|
return key;
|
|
3617
3728
|
}
|
|
3618
|
-
function signPayload(payload, key) {
|
|
3619
|
-
const body = base64UrlEncode(JSON.stringify(payload));
|
|
3620
|
-
const signature = createHmac("sha256", key).update(body).digest("base64url");
|
|
3621
|
-
return `${body}.${signature}`;
|
|
3622
|
-
}
|
|
3623
|
-
async function issueApprovalToken(payload, controlPlaneDir = defaultControlPlaneDir()) {
|
|
3624
|
-
const key = await loadOrCreateApprovalSigningKey(controlPlaneDir);
|
|
3625
|
-
return signPayload(payload, key);
|
|
3626
|
-
}
|
|
3627
3729
|
async function verifyApprovalToken(token, controlPlaneDir = defaultControlPlaneDir()) {
|
|
3628
3730
|
const [body, signature] = token.split(".");
|
|
3629
3731
|
if (!body || !signature) {
|
|
3630
3732
|
return null;
|
|
3631
3733
|
}
|
|
3632
3734
|
const keyPath = approvalSigningKeyPath(controlPlaneDir);
|
|
3633
|
-
if (!
|
|
3735
|
+
if (!existsSync4(keyPath)) {
|
|
3634
3736
|
return null;
|
|
3635
3737
|
}
|
|
3636
|
-
const key = await
|
|
3738
|
+
const key = await readFile4(keyPath);
|
|
3637
3739
|
const expected = createHmac("sha256", key).update(body).digest("base64url");
|
|
3638
3740
|
const actualBuffer = Buffer.from(signature);
|
|
3639
3741
|
const expectedBuffer = Buffer.from(expected);
|
|
@@ -3840,11 +3942,11 @@ init_approval_v3();
|
|
|
3840
3942
|
|
|
3841
3943
|
// src/core/capability/boundary-attestation-sign.ts
|
|
3842
3944
|
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
3843
|
-
import { access, readFile as
|
|
3945
|
+
import { access, readFile as readFile5 } from "node:fs/promises";
|
|
3844
3946
|
init_fingerprint2();
|
|
3845
3947
|
|
|
3846
3948
|
// src/core/capability/attestation.ts
|
|
3847
|
-
import
|
|
3949
|
+
import path13 from "node:path";
|
|
3848
3950
|
var BOUNDARY_ATTESTATION_VERSION = 1;
|
|
3849
3951
|
var CONTAINED_EXECUTION_ATTESTATION_VERSION = 1;
|
|
3850
3952
|
var KNOWN_DRIVERS = /* @__PURE__ */ new Set([
|
|
@@ -3912,7 +4014,7 @@ function validateContainedExecutionAttestation(value) {
|
|
|
3912
4014
|
const record = value;
|
|
3913
4015
|
const dockerSubstrate = record.dockerSubstrate;
|
|
3914
4016
|
const dockerConfiguration = record.dockerConfiguration;
|
|
3915
|
-
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" || !
|
|
4017
|
+
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" || !path13.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:///") || !path13.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" || !path13.isAbsolute(dockerConfiguration.executable) || /[\0\n\r]/.test(dockerConfiguration.executable) || typeof dockerConfiguration.host !== "string" || !dockerConfiguration.host.startsWith("unix:///") || !path13.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") {
|
|
3916
4018
|
return false;
|
|
3917
4019
|
}
|
|
3918
4020
|
const probedAt = Date.parse(record.probedAt);
|
|
@@ -3982,7 +4084,7 @@ async function verifySignedBoundaryAttestation(params) {
|
|
|
3982
4084
|
return record.attestation;
|
|
3983
4085
|
}
|
|
3984
4086
|
async function readSignedAttestationFile(filePath) {
|
|
3985
|
-
return JSON.parse(await
|
|
4087
|
+
return JSON.parse(await readFile5(filePath, "utf8"));
|
|
3986
4088
|
}
|
|
3987
4089
|
|
|
3988
4090
|
// src/core/capability/boundary-egress.ts
|
|
@@ -4151,13 +4253,13 @@ async function runWithBoundaryRunnable(target, params) {
|
|
|
4151
4253
|
}
|
|
4152
4254
|
|
|
4153
4255
|
// src/core/capability/boundary-session.ts
|
|
4154
|
-
import
|
|
4256
|
+
import path24 from "node:path";
|
|
4155
4257
|
|
|
4156
4258
|
// src/services/egress-service.ts
|
|
4157
|
-
import { existsSync as
|
|
4158
|
-
import { mkdir as
|
|
4259
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
|
|
4260
|
+
import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
|
|
4159
4261
|
import net from "node:net";
|
|
4160
|
-
import
|
|
4262
|
+
import path14 from "node:path";
|
|
4161
4263
|
init_config_io();
|
|
4162
4264
|
init_config();
|
|
4163
4265
|
|
|
@@ -4170,16 +4272,16 @@ function egressStatePaths(repoRoot, config) {
|
|
|
4170
4272
|
const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
|
|
4171
4273
|
return {
|
|
4172
4274
|
stateDir,
|
|
4173
|
-
pidPath:
|
|
4174
|
-
statusPath:
|
|
4275
|
+
pidPath: path14.join(stateDir, "egress-proxy.pid"),
|
|
4276
|
+
statusPath: path14.join(stateDir, "egress-proxy.json")
|
|
4175
4277
|
};
|
|
4176
4278
|
}
|
|
4177
4279
|
async function readStatusFile(statusPath) {
|
|
4178
|
-
if (!
|
|
4280
|
+
if (!existsSync5(statusPath)) {
|
|
4179
4281
|
return null;
|
|
4180
4282
|
}
|
|
4181
4283
|
try {
|
|
4182
|
-
const raw = JSON.parse(await
|
|
4284
|
+
const raw = JSON.parse(await readFile6(statusPath, "utf8"));
|
|
4183
4285
|
if (typeof raw.pid !== "number") {
|
|
4184
4286
|
return null;
|
|
4185
4287
|
}
|
|
@@ -4197,9 +4299,9 @@ async function readStatusFile(statusPath) {
|
|
|
4197
4299
|
async function isPortOpen(host, port) {
|
|
4198
4300
|
return new Promise((resolve) => {
|
|
4199
4301
|
const socket = net.createConnection({ host, port });
|
|
4200
|
-
const finish = (
|
|
4302
|
+
const finish = (open7) => {
|
|
4201
4303
|
socket.destroy();
|
|
4202
|
-
resolve(
|
|
4304
|
+
resolve(open7);
|
|
4203
4305
|
};
|
|
4204
4306
|
socket.setTimeout(300);
|
|
4205
4307
|
socket.on("connect", () => finish(true));
|
|
@@ -4210,7 +4312,7 @@ async function isPortOpen(host, port) {
|
|
|
4210
4312
|
async function resolveLiveEgressStatus(repoRoot, config) {
|
|
4211
4313
|
const { statusPath } = egressStatePaths(repoRoot, config);
|
|
4212
4314
|
const statusCandidates = [statusPath];
|
|
4213
|
-
const controlPlaneStatus =
|
|
4315
|
+
const controlPlaneStatus = path14.join(configuredControlPlaneDir(config), "egress-proxy.json");
|
|
4214
4316
|
if (!statusCandidates.includes(controlPlaneStatus)) {
|
|
4215
4317
|
statusCandidates.push(controlPlaneStatus);
|
|
4216
4318
|
}
|
|
@@ -4236,7 +4338,7 @@ function isProcessAlive(pid) {
|
|
|
4236
4338
|
}
|
|
4237
4339
|
}
|
|
4238
4340
|
async function egressStatus(options = {}) {
|
|
4239
|
-
const repoRoot =
|
|
4341
|
+
const repoRoot = path14.resolve(options.targetDir ?? process.cwd());
|
|
4240
4342
|
const config = await loadConfigFile(repoRoot);
|
|
4241
4343
|
const { status, host, port, portOccupied } = await resolveLiveEgressStatus(repoRoot, config);
|
|
4242
4344
|
const ownedRunning = Boolean(status);
|
|
@@ -4267,7 +4369,7 @@ init_config();
|
|
|
4267
4369
|
import { createHash as createHash8, randomUUID as randomUUID2 } from "node:crypto";
|
|
4268
4370
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
4269
4371
|
import { access as access2, constants as constants2, lstat as lstat4, mkdtemp as mkdtemp2, realpath as realpath2, rm as rm2 } from "node:fs/promises";
|
|
4270
|
-
import
|
|
4372
|
+
import path19 from "node:path";
|
|
4271
4373
|
init_fingerprint2();
|
|
4272
4374
|
init_path_utils();
|
|
4273
4375
|
|
|
@@ -4446,7 +4548,7 @@ function runProcessWithBoundedOutput(file, args, options, timeoutMs, outputPolic
|
|
|
4446
4548
|
}
|
|
4447
4549
|
|
|
4448
4550
|
// src/core/contained-execution/docker-policy.ts
|
|
4449
|
-
import
|
|
4551
|
+
import path15 from "node:path";
|
|
4450
4552
|
|
|
4451
4553
|
// src/core/contained-execution/policy.ts
|
|
4452
4554
|
var CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS = [
|
|
@@ -4575,7 +4677,7 @@ var PROXY_ENV_NAMES = [
|
|
|
4575
4677
|
var IMAGE_ID_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
4576
4678
|
var SAFE_CONTAINER_NAME = /^belay-contained-[0-9a-f-]{36}$/;
|
|
4577
4679
|
function assertSafeDockerPath(value, code) {
|
|
4578
|
-
if (!
|
|
4680
|
+
if (!path15.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
|
|
4579
4681
|
throw new ContainedExecutionFailureError(code);
|
|
4580
4682
|
}
|
|
4581
4683
|
}
|
|
@@ -4654,11 +4756,11 @@ init_path_utils();
|
|
|
4654
4756
|
import { createHash as createHash7 } from "node:crypto";
|
|
4655
4757
|
import { constants as fsConstants } from "node:fs";
|
|
4656
4758
|
import {
|
|
4657
|
-
chmod as
|
|
4759
|
+
chmod as chmod3,
|
|
4658
4760
|
lstat as lstat3,
|
|
4659
|
-
mkdir as
|
|
4761
|
+
mkdir as mkdir6,
|
|
4660
4762
|
mkdtemp,
|
|
4661
|
-
open as
|
|
4763
|
+
open as open4,
|
|
4662
4764
|
opendir,
|
|
4663
4765
|
readlink as readlink2,
|
|
4664
4766
|
realpath,
|
|
@@ -4666,41 +4768,41 @@ import {
|
|
|
4666
4768
|
symlink
|
|
4667
4769
|
} from "node:fs/promises";
|
|
4668
4770
|
import os from "node:os";
|
|
4669
|
-
import
|
|
4771
|
+
import path18 from "node:path";
|
|
4670
4772
|
|
|
4671
4773
|
// src/core/transactional/file-tree.ts
|
|
4672
4774
|
import { createHash as createHash6 } from "node:crypto";
|
|
4673
4775
|
import { lstat as lstat2, readdir } from "node:fs/promises";
|
|
4674
|
-
import
|
|
4776
|
+
import path17 from "node:path";
|
|
4675
4777
|
|
|
4676
4778
|
// src/core/transactional/file-tree-path.ts
|
|
4677
4779
|
init_path_utils();
|
|
4678
|
-
import
|
|
4780
|
+
import path16 from "node:path";
|
|
4679
4781
|
var FILE_CHECKPOINT_PATH_ESCAPE = "file_checkpoint_path_escape";
|
|
4680
4782
|
function validateRelativePath(relativePath) {
|
|
4681
4783
|
if (!relativePath || relativePath.includes("\0")) {
|
|
4682
4784
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
4683
4785
|
}
|
|
4684
|
-
if (
|
|
4786
|
+
if (path16.isAbsolute(relativePath)) {
|
|
4685
4787
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
4686
4788
|
}
|
|
4687
4789
|
if (isPathOutsideRoot(relativePath)) {
|
|
4688
4790
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
4689
4791
|
}
|
|
4690
|
-
const normalized =
|
|
4691
|
-
if (normalized === ".." || normalized.startsWith(`..${
|
|
4792
|
+
const normalized = path16.normalize(relativePath);
|
|
4793
|
+
if (normalized === ".." || normalized.startsWith(`..${path16.sep}`)) {
|
|
4692
4794
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
4693
4795
|
}
|
|
4694
4796
|
}
|
|
4695
4797
|
function joinRelativePath(root, relativePath) {
|
|
4696
4798
|
validateRelativePath(relativePath);
|
|
4697
|
-
return
|
|
4799
|
+
return path16.join(canonicalPath(root), relativePath);
|
|
4698
4800
|
}
|
|
4699
4801
|
function isRootGitMetadataPath(relativePath) {
|
|
4700
|
-
return relativePath === ".git" || relativePath.startsWith(`.git${
|
|
4802
|
+
return relativePath === ".git" || relativePath.startsWith(`.git${path16.sep}`);
|
|
4701
4803
|
}
|
|
4702
4804
|
function isNestedGitPath(relativePath) {
|
|
4703
|
-
const segments = relativePath.split(
|
|
4805
|
+
const segments = relativePath.split(path16.sep).filter(Boolean);
|
|
4704
4806
|
if (segments.length === 0) {
|
|
4705
4807
|
return false;
|
|
4706
4808
|
}
|
|
@@ -4867,7 +4969,7 @@ async function readPresentNode(absolutePath, counters) {
|
|
|
4867
4969
|
async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters, quotas, deadlineMs, entries) {
|
|
4868
4970
|
assertWithinDeadline(deadlineMs);
|
|
4869
4971
|
assertWithinQuotas(counters, quotas);
|
|
4870
|
-
const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) :
|
|
4972
|
+
const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path17.resolve(resourceRoot);
|
|
4871
4973
|
const dirInfo = await lstat2(absoluteDir);
|
|
4872
4974
|
if (!dirInfo.isDirectory()) {
|
|
4873
4975
|
throw new Error(FILE_CHECKPOINT_UNSUPPORTED_NODE);
|
|
@@ -4893,7 +4995,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
|
|
|
4893
4995
|
const names = await readdir(absoluteDir);
|
|
4894
4996
|
for (const name of names) {
|
|
4895
4997
|
assertWithinDeadline(deadlineMs);
|
|
4896
|
-
const childRelative = relativeDir ?
|
|
4998
|
+
const childRelative = relativeDir ? path17.join(relativeDir, name) : name;
|
|
4897
4999
|
validateRelativePath(childRelative);
|
|
4898
5000
|
if (isNestedGitPath(childRelative)) {
|
|
4899
5001
|
throw new Error(FILE_CHECKPOINT_NESTED_REPOSITORY);
|
|
@@ -4901,7 +5003,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
|
|
|
4901
5003
|
if (isExcludedTreePath(childRelative, excludedRoots, resourceRoot)) {
|
|
4902
5004
|
continue;
|
|
4903
5005
|
}
|
|
4904
|
-
const childAbsolute =
|
|
5006
|
+
const childAbsolute = path17.join(absoluteDir, name);
|
|
4905
5007
|
const childInfo = await lstat2(childAbsolute);
|
|
4906
5008
|
if (childInfo.isDirectory() && !childInfo.isSymbolicLink()) {
|
|
4907
5009
|
await walkDirectory(
|
|
@@ -5012,16 +5114,16 @@ function validateContainedExecutionMirrorLease(handle, expected) {
|
|
|
5012
5114
|
);
|
|
5013
5115
|
}
|
|
5014
5116
|
var productionDependencies = {
|
|
5015
|
-
makeTempRoot: () => mkdtemp(
|
|
5117
|
+
makeTempRoot: () => mkdtemp(path18.join(os.tmpdir(), "belay-contained-mirror-")),
|
|
5016
5118
|
removeRoot: (root) => rm(root, { recursive: true, force: true }),
|
|
5017
5119
|
now: () => Date.now()
|
|
5018
5120
|
};
|
|
5019
5121
|
function isAtOrWithin(root, target) {
|
|
5020
|
-
const relative =
|
|
5021
|
-
return relative === "" || !
|
|
5122
|
+
const relative = path18.relative(root, target);
|
|
5123
|
+
return relative === "" || !path18.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path18.sep}`);
|
|
5022
5124
|
}
|
|
5023
5125
|
function isGitMetadataRelativePath(relativePath) {
|
|
5024
|
-
return relativePath.split(
|
|
5126
|
+
return relativePath.split(path18.sep).some((segment) => segment.toLowerCase() === ".git");
|
|
5025
5127
|
}
|
|
5026
5128
|
function identityFromStats(stats) {
|
|
5027
5129
|
return {
|
|
@@ -5103,7 +5205,7 @@ async function readStableFile(absolutePath, context) {
|
|
|
5103
5205
|
if (before.nlink > 1n) {
|
|
5104
5206
|
throw new Error(FILE_CHECKPOINT_HARDLINK_UNSUPPORTED);
|
|
5105
5207
|
}
|
|
5106
|
-
const source = await
|
|
5208
|
+
const source = await open4(absolutePath, safeReadFlags());
|
|
5107
5209
|
try {
|
|
5108
5210
|
const opened = await source.stat({ bigint: true });
|
|
5109
5211
|
assertRegularSingleLinkBigInt(opened);
|
|
@@ -5158,7 +5260,7 @@ function addMetadataRoots(context, directoryPath) {
|
|
|
5158
5260
|
}
|
|
5159
5261
|
}
|
|
5160
5262
|
function pathMatchesRoots(absolutePath, roots) {
|
|
5161
|
-
const lexical =
|
|
5263
|
+
const lexical = path18.resolve(absolutePath);
|
|
5162
5264
|
const canonical = canonicalPath(absolutePath);
|
|
5163
5265
|
for (const root of roots) {
|
|
5164
5266
|
if (isAtOrWithin(root, lexical) || isAtOrWithin(root, canonical)) {
|
|
@@ -5168,7 +5270,7 @@ function pathMatchesRoots(absolutePath, roots) {
|
|
|
5168
5270
|
return false;
|
|
5169
5271
|
}
|
|
5170
5272
|
function pathLexicallyMatchesRoots(absolutePath, roots) {
|
|
5171
|
-
const lexical =
|
|
5273
|
+
const lexical = path18.resolve(absolutePath);
|
|
5172
5274
|
for (const root of roots) {
|
|
5173
5275
|
if (isAtOrWithin(root, lexical)) {
|
|
5174
5276
|
return true;
|
|
@@ -5189,11 +5291,11 @@ async function readSafeSymlink(absolutePath, context) {
|
|
|
5189
5291
|
}
|
|
5190
5292
|
const identity = identityFromStats(before);
|
|
5191
5293
|
const target = await readlink2(absolutePath);
|
|
5192
|
-
if (
|
|
5294
|
+
if (path18.isAbsolute(target)) {
|
|
5193
5295
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
|
|
5194
5296
|
}
|
|
5195
|
-
const lexicalTarget =
|
|
5196
|
-
if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(
|
|
5297
|
+
const lexicalTarget = path18.resolve(path18.dirname(absolutePath), target);
|
|
5298
|
+
if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path18.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
|
|
5197
5299
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
|
|
5198
5300
|
}
|
|
5199
5301
|
let resolvedTarget;
|
|
@@ -5229,7 +5331,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
|
|
|
5229
5331
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5230
5332
|
}
|
|
5231
5333
|
const directoryFlags = safeReadFlags() | (fsConstants.O_DIRECTORY ?? 0);
|
|
5232
|
-
const directory = await
|
|
5334
|
+
const directory = await open4(absoluteDirectory, directoryFlags);
|
|
5233
5335
|
try {
|
|
5234
5336
|
const opened = await directory.stat({ bigint: true });
|
|
5235
5337
|
if (!opened.isDirectory() || !identitiesEqual(beforeIdentity, identityFromStats(opened))) {
|
|
@@ -5238,7 +5340,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
|
|
|
5238
5340
|
const entriesDirectory = await opendir(absoluteDirectory, { bufferSize: 32 });
|
|
5239
5341
|
for await (const directoryEntry of entriesDirectory) {
|
|
5240
5342
|
assertDeadline(context.deadlineMs, context.now);
|
|
5241
|
-
const relativePath = relativeDirectory ?
|
|
5343
|
+
const relativePath = relativeDirectory ? path18.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
|
|
5242
5344
|
const absolutePath = joinRelativePath(context.sourceRoot, relativePath);
|
|
5243
5345
|
const info = await lstat3(absolutePath);
|
|
5244
5346
|
if (info.isSymbolicLink()) {
|
|
@@ -5322,7 +5424,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5322
5424
|
}
|
|
5323
5425
|
assertEntryIdentity(entry, identityFromStats(before));
|
|
5324
5426
|
await context.beforeCopyOpen?.(sourcePath);
|
|
5325
|
-
const source = await
|
|
5427
|
+
const source = await open4(sourcePath, safeReadFlags());
|
|
5326
5428
|
let destination;
|
|
5327
5429
|
try {
|
|
5328
5430
|
const opened = await source.stat({ bigint: true });
|
|
@@ -5332,8 +5434,8 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5332
5434
|
if (entry.node.kind !== "file") {
|
|
5333
5435
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5334
5436
|
}
|
|
5335
|
-
await
|
|
5336
|
-
destination = await
|
|
5437
|
+
await mkdir6(path18.dirname(destinationPath), { recursive: true, mode: 448 });
|
|
5438
|
+
destination = await open4(
|
|
5337
5439
|
destinationPath,
|
|
5338
5440
|
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
|
|
5339
5441
|
safePermissionMode(opened.mode)
|
|
@@ -5375,7 +5477,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5375
5477
|
if (entry.node.kind !== "file") {
|
|
5376
5478
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5377
5479
|
}
|
|
5378
|
-
await
|
|
5480
|
+
await chmod3(destinationPath, safePermissionMode(entry.node.mode));
|
|
5379
5481
|
}
|
|
5380
5482
|
async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, context) {
|
|
5381
5483
|
const directoryEntries = [];
|
|
@@ -5384,7 +5486,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
5384
5486
|
const sourcePath = joinRelativePath(sourceRoot, entry.relativePath);
|
|
5385
5487
|
const destinationPath = joinRelativePath(destinationRoot, entry.relativePath);
|
|
5386
5488
|
if (entry.node.kind === "directory") {
|
|
5387
|
-
await
|
|
5489
|
+
await mkdir6(destinationPath, { recursive: true, mode: 448 });
|
|
5388
5490
|
directoryEntries.push(entry);
|
|
5389
5491
|
continue;
|
|
5390
5492
|
}
|
|
@@ -5396,7 +5498,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
5396
5498
|
if (current !== entry.node.target) {
|
|
5397
5499
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5398
5500
|
}
|
|
5399
|
-
await
|
|
5501
|
+
await mkdir6(path18.dirname(destinationPath), { recursive: true, mode: 448 });
|
|
5400
5502
|
await symlink(entry.node.target, destinationPath);
|
|
5401
5503
|
continue;
|
|
5402
5504
|
}
|
|
@@ -5406,12 +5508,12 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
5406
5508
|
if (entry.node.kind !== "directory") {
|
|
5407
5509
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5408
5510
|
}
|
|
5409
|
-
await
|
|
5511
|
+
await chmod3(
|
|
5410
5512
|
joinRelativePath(destinationRoot, entry.relativePath),
|
|
5411
5513
|
safePermissionMode(entry.node.mode)
|
|
5412
5514
|
);
|
|
5413
5515
|
}
|
|
5414
|
-
await
|
|
5516
|
+
await chmod3(destinationRoot, 448);
|
|
5415
5517
|
}
|
|
5416
5518
|
async function assertStableCopiedSnapshot(sourceRoot, destinationRoot, protectedRoots, limits, deadlineMs, now, before) {
|
|
5417
5519
|
const [after, copied] = await Promise.all([
|
|
@@ -5456,7 +5558,7 @@ async function cleanupOwnedRoot(root, dependencies) {
|
|
|
5456
5558
|
}
|
|
5457
5559
|
async function prepareWithDependencies(options, dependencies) {
|
|
5458
5560
|
validateOptions(options);
|
|
5459
|
-
const guestWorkspacePath =
|
|
5561
|
+
const guestWorkspacePath = path18.resolve(options.sourceRoot);
|
|
5460
5562
|
const sourceRoot = canonicalPath(options.sourceRoot);
|
|
5461
5563
|
const protectedRoots = options.controlPlaneRoots.map((root) => canonicalPath(root));
|
|
5462
5564
|
if (pathMatchesRoots(sourceRoot, protectedRoots)) {
|
|
@@ -5464,7 +5566,7 @@ async function prepareWithDependencies(options, dependencies) {
|
|
|
5464
5566
|
}
|
|
5465
5567
|
const guestRoot = await dependencies.makeTempRoot();
|
|
5466
5568
|
try {
|
|
5467
|
-
await
|
|
5569
|
+
await chmod3(guestRoot, 448);
|
|
5468
5570
|
const deadlineMs = dependencies.now() + options.limits.prepareTimeoutMs;
|
|
5469
5571
|
const before = await buildSafeMirrorSnapshot(
|
|
5470
5572
|
sourceRoot,
|
|
@@ -5492,7 +5594,7 @@ async function prepareWithDependencies(options, dependencies) {
|
|
|
5492
5594
|
dependencies.now,
|
|
5493
5595
|
before
|
|
5494
5596
|
);
|
|
5495
|
-
await
|
|
5597
|
+
await chmod3(guestRoot, 448);
|
|
5496
5598
|
const lease = {
|
|
5497
5599
|
sourceRoot,
|
|
5498
5600
|
hostMirrorRoot: guestRoot,
|
|
@@ -5626,14 +5728,14 @@ async function digestFile(file) {
|
|
|
5626
5728
|
return hash.digest("hex");
|
|
5627
5729
|
}
|
|
5628
5730
|
async function resolveConfiguredDockerSubstrate(params) {
|
|
5629
|
-
if (!
|
|
5731
|
+
if (!path19.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
|
|
5630
5732
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_binary_invalid");
|
|
5631
5733
|
}
|
|
5632
5734
|
if (!params.host.startsWith("unix:///") || /[\0\n\r]/.test(params.host)) {
|
|
5633
5735
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
|
|
5634
5736
|
}
|
|
5635
5737
|
const configuredSocket = params.host.slice("unix://".length);
|
|
5636
|
-
if (!
|
|
5738
|
+
if (!path19.isAbsolute(configuredSocket)) {
|
|
5637
5739
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
|
|
5638
5740
|
}
|
|
5639
5741
|
let binaryPath;
|
|
@@ -5894,7 +5996,7 @@ async function validatedGuestCwd(params) {
|
|
|
5894
5996
|
const requiredExclusions = [
|
|
5895
5997
|
...new Set([params.controlPlaneDir, ...params.protectedRoots].map(canonicalPath))
|
|
5896
5998
|
];
|
|
5897
|
-
if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !==
|
|
5999
|
+
if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path19.resolve(params.repoRoot) || !path19.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
|
|
5898
6000
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
5899
6001
|
const resolvedRoot = await realpath2(params.mirror.hostMirrorRoot);
|
|
5900
6002
|
const protectedRoots = [canonicalPath(params.repoRoot), ...requiredExclusions];
|
|
@@ -5906,10 +6008,10 @@ async function validatedGuestCwd(params) {
|
|
|
5906
6008
|
protectedRoots: requiredExclusions
|
|
5907
6009
|
}))
|
|
5908
6010
|
throw new ContainedExecutionFailureError("contained_execution_invalid_mirror_lease");
|
|
5909
|
-
const relative =
|
|
6011
|
+
const relative = path19.relative(params.mirror.guestWorkspacePath, path19.resolve(params.guestCwd));
|
|
5910
6012
|
let resolvedCwd;
|
|
5911
6013
|
try {
|
|
5912
|
-
resolvedCwd = await realpath2(
|
|
6014
|
+
resolvedCwd = await realpath2(path19.join(resolvedRoot, relative));
|
|
5913
6015
|
} catch {
|
|
5914
6016
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
5915
6017
|
}
|
|
@@ -5917,7 +6019,7 @@ async function validatedGuestCwd(params) {
|
|
|
5917
6019
|
if (!info.isDirectory() || !pathWithinRoot(resolvedRoot, resolvedCwd)) {
|
|
5918
6020
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
5919
6021
|
}
|
|
5920
|
-
return
|
|
6022
|
+
return path19.join(params.mirror.guestWorkspacePath, path19.relative(resolvedRoot, resolvedCwd));
|
|
5921
6023
|
}
|
|
5922
6024
|
function exactKeys(value, allowed) {
|
|
5923
6025
|
const names = new Set(allowed);
|
|
@@ -6144,19 +6246,19 @@ async function executeContainedDocker(params) {
|
|
|
6144
6246
|
init_path_utils();
|
|
6145
6247
|
import { spawn as spawn3 } from "node:child_process";
|
|
6146
6248
|
import os3 from "node:os";
|
|
6147
|
-
import
|
|
6249
|
+
import path21 from "node:path";
|
|
6148
6250
|
|
|
6149
6251
|
// src/core/transactional/apply-observed-changes.ts
|
|
6150
|
-
import { copyFile, lstat as lstat5, mkdir as
|
|
6252
|
+
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";
|
|
6151
6253
|
import os2 from "node:os";
|
|
6152
|
-
import
|
|
6254
|
+
import path20 from "node:path";
|
|
6153
6255
|
var TRANSACTIONAL_APPLY_TOCTOU = "transactional_apply_toctou";
|
|
6154
6256
|
var TRANSACTIONAL_APPLY_CONFLICT = "transactional_apply_conflict";
|
|
6155
6257
|
var TRANSACTIONAL_APPLY_ROLLBACK_FAILED = "transactional_apply_rollback_failed";
|
|
6156
6258
|
async function chmodSafe(target, mode) {
|
|
6157
6259
|
try {
|
|
6158
|
-
const { chmod:
|
|
6159
|
-
await
|
|
6260
|
+
const { chmod: chmod5 } = await import("node:fs/promises");
|
|
6261
|
+
await chmod5(target, mode & 511);
|
|
6160
6262
|
} catch {
|
|
6161
6263
|
}
|
|
6162
6264
|
}
|
|
@@ -6190,13 +6292,13 @@ async function copyPathPreservingType(source, target) {
|
|
|
6190
6292
|
}
|
|
6191
6293
|
}
|
|
6192
6294
|
await removePathIfExists(target);
|
|
6193
|
-
await
|
|
6295
|
+
await mkdir7(path20.dirname(target), { recursive: true });
|
|
6194
6296
|
if (info.isSymbolicLink()) {
|
|
6195
6297
|
await symlink2(await readlink3(source), target);
|
|
6196
6298
|
return;
|
|
6197
6299
|
}
|
|
6198
6300
|
if (info.isDirectory() && !info.isSymbolicLink()) {
|
|
6199
|
-
await
|
|
6301
|
+
await mkdir7(target, { recursive: true, mode: info.mode & 511 });
|
|
6200
6302
|
return;
|
|
6201
6303
|
}
|
|
6202
6304
|
if (!info.isFile()) {
|
|
@@ -6206,12 +6308,12 @@ async function copyPathPreservingType(source, target) {
|
|
|
6206
6308
|
await chmodSafe(target, info.mode);
|
|
6207
6309
|
}
|
|
6208
6310
|
async function assertParentChainSafe(targetRoot, relativePath, plannedDirectories = /* @__PURE__ */ new Set()) {
|
|
6209
|
-
const segments = relativePath.split(
|
|
6311
|
+
const segments = relativePath.split(path20.sep).filter(Boolean);
|
|
6210
6312
|
if (segments.length <= 1) {
|
|
6211
6313
|
return;
|
|
6212
6314
|
}
|
|
6213
6315
|
for (let index = 1; index < segments.length; index++) {
|
|
6214
|
-
const prefix = segments.slice(0, index).join(
|
|
6316
|
+
const prefix = segments.slice(0, index).join(path20.sep);
|
|
6215
6317
|
const absolute = joinRelativePath(targetRoot, prefix);
|
|
6216
6318
|
let info;
|
|
6217
6319
|
try {
|
|
@@ -6231,8 +6333,8 @@ async function assertParentChainSafe(targetRoot, relativePath, plannedDirectorie
|
|
|
6231
6333
|
}
|
|
6232
6334
|
}
|
|
6233
6335
|
async function restorePathFromBackup(backupPath, target, rollbackRoot) {
|
|
6234
|
-
const stagingRoot = await mkdtemp3(
|
|
6235
|
-
const staged =
|
|
6336
|
+
const stagingRoot = await mkdtemp3(path20.join(rollbackRoot, "restore-"));
|
|
6337
|
+
const staged = path20.join(stagingRoot, "node");
|
|
6236
6338
|
try {
|
|
6237
6339
|
await copyPathPreservingType(backupPath, staged);
|
|
6238
6340
|
await copyPathPreservingType(staged, target);
|
|
@@ -6316,7 +6418,7 @@ async function applySingleChange(sourceRoot, targetRoot, change) {
|
|
|
6316
6418
|
if (change.before.kind !== "directory") {
|
|
6317
6419
|
await removePathIfExists(target);
|
|
6318
6420
|
}
|
|
6319
|
-
await
|
|
6421
|
+
await mkdir7(target, { recursive: true, mode: change.after.mode & 511 });
|
|
6320
6422
|
await chmodSafe(target, change.after.mode);
|
|
6321
6423
|
return;
|
|
6322
6424
|
}
|
|
@@ -6335,7 +6437,7 @@ async function applyObservedChanges(params) {
|
|
|
6335
6437
|
await assertParentChainSafe(targetRoot, change.relativePath, plannedDirectories);
|
|
6336
6438
|
await assertTargetMatches(targetRoot, change);
|
|
6337
6439
|
}
|
|
6338
|
-
const backupRoot = await mkdtemp3(
|
|
6440
|
+
const backupRoot = await mkdtemp3(path20.join(os2.tmpdir(), "belay-tx-rollback-"));
|
|
6339
6441
|
const rollbackActions = [];
|
|
6340
6442
|
let mutationAttempted = false;
|
|
6341
6443
|
let resourceIdentityChanged = false;
|
|
@@ -6354,12 +6456,12 @@ async function applyObservedChanges(params) {
|
|
|
6354
6456
|
targetExists = false;
|
|
6355
6457
|
}
|
|
6356
6458
|
if (targetExists) {
|
|
6357
|
-
const backupPath =
|
|
6459
|
+
const backupPath = path20.join(
|
|
6358
6460
|
backupRoot,
|
|
6359
6461
|
String(rollbackActions.length),
|
|
6360
6462
|
change.relativePath
|
|
6361
6463
|
);
|
|
6362
|
-
await
|
|
6464
|
+
await mkdir7(path20.dirname(backupPath), { recursive: true });
|
|
6363
6465
|
await copyPathPreservingType(target, backupPath);
|
|
6364
6466
|
await assertParentChainSafe(targetRoot, change.relativePath);
|
|
6365
6467
|
rollbackActions.push({
|
|
@@ -6458,7 +6560,7 @@ function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
|
|
|
6458
6560
|
if (ignoreRoots.length === 0) {
|
|
6459
6561
|
return false;
|
|
6460
6562
|
}
|
|
6461
|
-
const absolutePath = canonicalPath(
|
|
6563
|
+
const absolutePath = canonicalPath(path21.join(repoRoot, relativePath));
|
|
6462
6564
|
return ignoreRoots.some(
|
|
6463
6565
|
(root) => pathWithinRoot(root, absolutePath) || root === absolutePath || pathWithinRoot(absolutePath, root)
|
|
6464
6566
|
);
|
|
@@ -6496,7 +6598,7 @@ async function isDirtyWorktree(repoRoot, options) {
|
|
|
6496
6598
|
}
|
|
6497
6599
|
async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
6498
6600
|
const { mkdtemp: mkdtemp6, rm: rm10 } = await import("node:fs/promises");
|
|
6499
|
-
const worktreePath = await mkdtemp6(
|
|
6601
|
+
const worktreePath = await mkdtemp6(path21.join(os3.tmpdir(), "belay-tx-"));
|
|
6500
6602
|
await execGit(repoRoot, ["worktree", "add", "--detach", worktreePath, "HEAD"]);
|
|
6501
6603
|
return {
|
|
6502
6604
|
worktreePath,
|
|
@@ -6515,14 +6617,14 @@ async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
|
6515
6617
|
}
|
|
6516
6618
|
function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
|
|
6517
6619
|
const resolvedCwd = canonicalPath(cwd);
|
|
6518
|
-
const relative =
|
|
6519
|
-
if (isPathOutsideRoot(relative) ||
|
|
6620
|
+
const relative = path21.relative(canonicalPath(repoRoot), resolvedCwd);
|
|
6621
|
+
if (isPathOutsideRoot(relative) || path21.isAbsolute(relative)) {
|
|
6520
6622
|
return worktreePath;
|
|
6521
6623
|
}
|
|
6522
6624
|
if (relative === "") {
|
|
6523
6625
|
return worktreePath;
|
|
6524
6626
|
}
|
|
6525
|
-
return
|
|
6627
|
+
return path21.join(worktreePath, relative);
|
|
6526
6628
|
}
|
|
6527
6629
|
function runShellCommand(command, cwd, timeoutMs) {
|
|
6528
6630
|
return runProcessWithBoundedOutput(command, [], { cwd, shell: true, env: process.env }, timeoutMs);
|
|
@@ -6587,7 +6689,7 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
6587
6689
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
6588
6690
|
|
|
6589
6691
|
// src/core/capability/boundary-grant-materialize.ts
|
|
6590
|
-
import
|
|
6692
|
+
import path22 from "node:path";
|
|
6591
6693
|
|
|
6592
6694
|
// src/core/glob.ts
|
|
6593
6695
|
init_approval();
|
|
@@ -6747,7 +6849,7 @@ function grantMatchesRequest(grant, request) {
|
|
|
6747
6849
|
var BOUNDARY_GRANT_TTL_MS = 15 * 6e4;
|
|
6748
6850
|
var BOUNDARY_GRANT_ISSUER_CONTAINER = "boundary:container";
|
|
6749
6851
|
function resolveCapabilityPath(targetPath, cwd) {
|
|
6750
|
-
const joined =
|
|
6852
|
+
const joined = path22.isAbsolute(targetPath) ? targetPath : path22.join(cwd, targetPath);
|
|
6751
6853
|
return canonicalPath(joined);
|
|
6752
6854
|
}
|
|
6753
6855
|
function isPathWithinBoundaryMount(request) {
|
|
@@ -6822,7 +6924,7 @@ function materializeContainerBoundaryGrant(request, params) {
|
|
|
6822
6924
|
|
|
6823
6925
|
// src/core/capability/boundary-workspace-mount.ts
|
|
6824
6926
|
init_path_utils();
|
|
6825
|
-
import
|
|
6927
|
+
import path23 from "node:path";
|
|
6826
6928
|
var HOST_PATH_ENV_VARS = [
|
|
6827
6929
|
"BELAY_EGRESS_REPO_ROOT",
|
|
6828
6930
|
"BELAY_JUDGE_BROKER_REPO_ROOT",
|
|
@@ -6847,23 +6949,23 @@ function validateWorkspaceMount(mount) {
|
|
|
6847
6949
|
if (mount.cwdRelative.includes("\0")) {
|
|
6848
6950
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
6849
6951
|
}
|
|
6850
|
-
const normalizedRelative =
|
|
6952
|
+
const normalizedRelative = path23.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
|
|
6851
6953
|
if (normalizedRelative === ".." || normalizedRelative.startsWith("../")) {
|
|
6852
6954
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
6853
6955
|
}
|
|
6854
|
-
if (
|
|
6956
|
+
if (path23.posix.isAbsolute(normalizedRelative)) {
|
|
6855
6957
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
6856
6958
|
}
|
|
6857
6959
|
}
|
|
6858
6960
|
function resolveGuestWorkdir(mount) {
|
|
6859
6961
|
validateWorkspaceMount(mount);
|
|
6860
6962
|
const guestTargetRoot = canonicalPath(mount.guestTargetRoot);
|
|
6861
|
-
const normalizedRelative =
|
|
6963
|
+
const normalizedRelative = path23.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
|
|
6862
6964
|
if (normalizedRelative === "." || normalizedRelative === "") {
|
|
6863
6965
|
return guestTargetRoot;
|
|
6864
6966
|
}
|
|
6865
6967
|
const segments = normalizedRelative.split("/").filter(Boolean);
|
|
6866
|
-
return
|
|
6968
|
+
return path23.posix.join(guestTargetRoot, ...segments);
|
|
6867
6969
|
}
|
|
6868
6970
|
function buildWorkspaceMountSpec(mount) {
|
|
6869
6971
|
validateWorkspaceMount(mount);
|
|
@@ -7154,7 +7256,7 @@ function containedExecutionFresh(attestation, config, now = Date.now()) {
|
|
|
7154
7256
|
}
|
|
7155
7257
|
function boundaryAttestationPath(repoRoot, config) {
|
|
7156
7258
|
const rel = config.capability?.attestationRelPath ?? ".belay/attestation.json";
|
|
7157
|
-
return
|
|
7259
|
+
return path24.isAbsolute(rel) ? rel : path24.join(repoRoot, rel);
|
|
7158
7260
|
}
|
|
7159
7261
|
async function loadBoundaryAttestation(filePath, expectedRepoRoot, controlPlaneDir) {
|
|
7160
7262
|
try {
|
|
@@ -7259,7 +7361,7 @@ init_capability_request_hash();
|
|
|
7259
7361
|
|
|
7260
7362
|
// src/core/capability/gate-policy-shadow.ts
|
|
7261
7363
|
init_config();
|
|
7262
|
-
import
|
|
7364
|
+
import path30 from "node:path";
|
|
7263
7365
|
|
|
7264
7366
|
// src/core/effect-ir/audit.ts
|
|
7265
7367
|
init_fingerprint2();
|
|
@@ -7353,8 +7455,8 @@ function evidenceRank(level) {
|
|
|
7353
7455
|
}
|
|
7354
7456
|
|
|
7355
7457
|
// src/core/effect-ir/package-exec.ts
|
|
7356
|
-
import { existsSync as
|
|
7357
|
-
import
|
|
7458
|
+
import { existsSync as existsSync6, realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
|
|
7459
|
+
import path25 from "node:path";
|
|
7358
7460
|
|
|
7359
7461
|
// src/core/network-endpoint.ts
|
|
7360
7462
|
var SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "ssh:", "git:"]);
|
|
@@ -7645,18 +7747,18 @@ function classifyPackageAcquisitionSpec(spec) {
|
|
|
7645
7747
|
return { kind: "registry" };
|
|
7646
7748
|
}
|
|
7647
7749
|
function resolveLocalBin(binName, cwd, repoRoot) {
|
|
7648
|
-
const base =
|
|
7750
|
+
const base = path25.basename(binName);
|
|
7649
7751
|
if (!base || base === "." || base === "..") {
|
|
7650
7752
|
return null;
|
|
7651
7753
|
}
|
|
7652
|
-
let current =
|
|
7653
|
-
const stop =
|
|
7754
|
+
let current = path25.resolve(cwd);
|
|
7755
|
+
const stop = path25.resolve(repoRoot);
|
|
7654
7756
|
if (!pathWithinRoot(stop, current)) {
|
|
7655
7757
|
return null;
|
|
7656
7758
|
}
|
|
7657
7759
|
while (true) {
|
|
7658
|
-
const candidate =
|
|
7659
|
-
if (
|
|
7760
|
+
const candidate = path25.join(current, "node_modules", ".bin", base);
|
|
7761
|
+
if (existsSync6(candidate)) {
|
|
7660
7762
|
try {
|
|
7661
7763
|
const resolvedCandidate = realpathSync3.native(candidate);
|
|
7662
7764
|
if (!pathWithinRoot(stop, resolvedCandidate) || !statSync2(resolvedCandidate).isFile()) {
|
|
@@ -7670,7 +7772,7 @@ function resolveLocalBin(binName, cwd, repoRoot) {
|
|
|
7670
7772
|
if (current === stop) {
|
|
7671
7773
|
break;
|
|
7672
7774
|
}
|
|
7673
|
-
const parent =
|
|
7775
|
+
const parent = path25.dirname(current);
|
|
7674
7776
|
if (parent === current) {
|
|
7675
7777
|
break;
|
|
7676
7778
|
}
|
|
@@ -7865,7 +7967,7 @@ init_path_utils();
|
|
|
7865
7967
|
|
|
7866
7968
|
// src/core/verdict/containment.ts
|
|
7867
7969
|
init_git_resource_identity();
|
|
7868
|
-
import
|
|
7970
|
+
import path26 from "node:path";
|
|
7869
7971
|
init_path_utils();
|
|
7870
7972
|
|
|
7871
7973
|
// src/core/verdict/persistent-paths.ts
|
|
@@ -7885,7 +7987,7 @@ function expandHome(token) {
|
|
|
7885
7987
|
if (!home) {
|
|
7886
7988
|
return token;
|
|
7887
7989
|
}
|
|
7888
|
-
return token === "~" ? home :
|
|
7990
|
+
return token === "~" ? home : path26.join(home, token.slice(2));
|
|
7889
7991
|
}
|
|
7890
7992
|
return token;
|
|
7891
7993
|
}
|
|
@@ -7897,10 +7999,10 @@ function resolveTrustedPath(token, trustedCwd, trusted) {
|
|
|
7897
7999
|
return null;
|
|
7898
8000
|
}
|
|
7899
8001
|
const expanded = expandHome(token);
|
|
7900
|
-
if (
|
|
8002
|
+
if (path26.isAbsolute(expanded)) {
|
|
7901
8003
|
return canonicalPath(expanded);
|
|
7902
8004
|
}
|
|
7903
|
-
return canonicalPath(
|
|
8005
|
+
return canonicalPath(path26.resolve(trustedCwd, expanded));
|
|
7904
8006
|
}
|
|
7905
8007
|
function isGitPath(resolvedPath, repoRoot) {
|
|
7906
8008
|
if (isGitMetadataPath(resolvedPath, repoRoot)) {
|
|
@@ -8017,20 +8119,20 @@ function parseTier1Json(raw) {
|
|
|
8017
8119
|
init_judge_runtime_config();
|
|
8018
8120
|
|
|
8019
8121
|
// src/core/verdict/judge-session-kill-switch.ts
|
|
8020
|
-
import { existsSync as
|
|
8021
|
-
import { mkdir as
|
|
8022
|
-
import
|
|
8122
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
8123
|
+
import { mkdir as mkdir8, readFile as readFile7, unlink as unlink5, writeFile as writeFile4 } from "node:fs/promises";
|
|
8124
|
+
import path27 from "node:path";
|
|
8023
8125
|
var JUDGE_SESSION_KILL_FILE = "judge-session-kill.json";
|
|
8024
8126
|
function judgeSessionKillSwitchPath(stateDir) {
|
|
8025
|
-
return
|
|
8127
|
+
return path27.join(stateDir, JUDGE_SESSION_KILL_FILE);
|
|
8026
8128
|
}
|
|
8027
8129
|
async function readJudgeSessionKillSwitch(stateDir) {
|
|
8028
8130
|
const filePath = judgeSessionKillSwitchPath(stateDir);
|
|
8029
|
-
if (!
|
|
8131
|
+
if (!existsSync7(filePath)) {
|
|
8030
8132
|
return null;
|
|
8031
8133
|
}
|
|
8032
8134
|
try {
|
|
8033
|
-
const raw = JSON.parse(await
|
|
8135
|
+
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
8034
8136
|
return raw.triggered === true ? raw : null;
|
|
8035
8137
|
} catch {
|
|
8036
8138
|
return null;
|
|
@@ -8041,7 +8143,7 @@ async function isJudgeSessionKillSwitchPersisted(stateDir) {
|
|
|
8041
8143
|
return record?.triggered === true;
|
|
8042
8144
|
}
|
|
8043
8145
|
async function persistJudgeSessionKillSwitch(stateDir, reason = "shadow_mismatch") {
|
|
8044
|
-
await
|
|
8146
|
+
await mkdir8(stateDir, { recursive: true, mode: 448 });
|
|
8045
8147
|
const record = {
|
|
8046
8148
|
triggered: true,
|
|
8047
8149
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8202,10 +8304,10 @@ function recordJudgeLatency(phase, latencyMs, at = Date.now()) {
|
|
|
8202
8304
|
|
|
8203
8305
|
// src/core/verdict/judge-broker-service.ts
|
|
8204
8306
|
import { spawn as spawn6 } from "node:child_process";
|
|
8205
|
-
import { existsSync as
|
|
8206
|
-
import { unlink as
|
|
8307
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
8308
|
+
import { unlink as unlink6 } from "node:fs/promises";
|
|
8207
8309
|
import net2 from "node:net";
|
|
8208
|
-
import
|
|
8310
|
+
import path28 from "node:path";
|
|
8209
8311
|
import { fileURLToPath } from "node:url";
|
|
8210
8312
|
|
|
8211
8313
|
// src/core/verdict/judge-cli.ts
|
|
@@ -8751,10 +8853,10 @@ var JUDGE_BROKER_SESSION = "judge-broker-session.json";
|
|
|
8751
8853
|
function judgeBrokerPaths(stateDir) {
|
|
8752
8854
|
return {
|
|
8753
8855
|
stateDir,
|
|
8754
|
-
socketPath:
|
|
8755
|
-
statusPath:
|
|
8756
|
-
pidPath:
|
|
8757
|
-
sessionConfigPath:
|
|
8856
|
+
socketPath: path28.join(stateDir, JUDGE_BROKER_SOCKET),
|
|
8857
|
+
statusPath: path28.join(stateDir, JUDGE_BROKER_STATUS),
|
|
8858
|
+
pidPath: path28.join(stateDir, JUDGE_BROKER_PID),
|
|
8859
|
+
sessionConfigPath: path28.join(stateDir, JUDGE_BROKER_SESSION)
|
|
8758
8860
|
};
|
|
8759
8861
|
}
|
|
8760
8862
|
function daemonScriptPath() {
|
|
@@ -8772,12 +8874,12 @@ function useInProcessBroker(env = process.env) {
|
|
|
8772
8874
|
return Boolean(env.VITEST || env.VITEST_WORKER_ID || env.BELAY_JUDGE_BROKER_IN_PROCESS === "1");
|
|
8773
8875
|
}
|
|
8774
8876
|
async function readBrokerStatus(statusPath) {
|
|
8775
|
-
if (!
|
|
8877
|
+
if (!existsSync8(statusPath)) {
|
|
8776
8878
|
return null;
|
|
8777
8879
|
}
|
|
8778
8880
|
try {
|
|
8779
|
-
const { readFile:
|
|
8780
|
-
const raw = JSON.parse(await
|
|
8881
|
+
const { readFile: readFile17 } = await import("node:fs/promises");
|
|
8882
|
+
const raw = JSON.parse(await readFile17(statusPath, "utf8"));
|
|
8781
8883
|
if (typeof raw.pid !== "number" || typeof raw.socketPath !== "string") {
|
|
8782
8884
|
return null;
|
|
8783
8885
|
}
|
|
@@ -8787,13 +8889,13 @@ async function readBrokerStatus(statusPath) {
|
|
|
8787
8889
|
}
|
|
8788
8890
|
}
|
|
8789
8891
|
async function readBrokerSessionConfig(sessionConfigPath) {
|
|
8790
|
-
if (!
|
|
8892
|
+
if (!existsSync8(sessionConfigPath)) {
|
|
8791
8893
|
return null;
|
|
8792
8894
|
}
|
|
8793
8895
|
try {
|
|
8794
|
-
const { readFile:
|
|
8896
|
+
const { readFile: readFile17 } = await import("node:fs/promises");
|
|
8795
8897
|
const raw = JSON.parse(
|
|
8796
|
-
await
|
|
8898
|
+
await readFile17(sessionConfigPath, "utf8")
|
|
8797
8899
|
);
|
|
8798
8900
|
return normalizeJudgeSessionConfig({ ...raw, enabled: true });
|
|
8799
8901
|
} catch {
|
|
@@ -8884,7 +8986,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
|
|
|
8884
8986
|
const existingConfig = await readBrokerSessionConfig(paths.sessionConfigPath);
|
|
8885
8987
|
const configChanged = existingConfig !== null && brokerSessionConfigPayload(existingConfig) !== nextConfigPayload;
|
|
8886
8988
|
const status = await readBrokerStatus(paths.statusPath);
|
|
8887
|
-
if (status && isProcessAlive2(status.pid) &&
|
|
8989
|
+
if (status && isProcessAlive2(status.pid) && existsSync8(paths.socketPath) && !configChanged) {
|
|
8888
8990
|
await writeBrokerSessionConfig(paths, brokerSessionConfig);
|
|
8889
8991
|
return paths;
|
|
8890
8992
|
}
|
|
@@ -8906,7 +9008,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
|
|
|
8906
9008
|
child.unref();
|
|
8907
9009
|
const deadline = Date.now() + sessionConfig.connectTimeoutMs;
|
|
8908
9010
|
while (Date.now() < deadline) {
|
|
8909
|
-
if (
|
|
9011
|
+
if (existsSync8(paths.socketPath)) {
|
|
8910
9012
|
const live = await readBrokerStatus(paths.statusPath);
|
|
8911
9013
|
if (live && isProcessAlive2(live.pid)) {
|
|
8912
9014
|
return paths;
|
|
@@ -8923,8 +9025,8 @@ async function cleanupJudgeBrokerArtifacts(paths) {
|
|
|
8923
9025
|
paths.pidPath,
|
|
8924
9026
|
paths.sessionConfigPath
|
|
8925
9027
|
]) {
|
|
8926
|
-
if (
|
|
8927
|
-
await
|
|
9028
|
+
if (existsSync8(artifact)) {
|
|
9029
|
+
await unlink6(artifact).catch(() => void 0);
|
|
8928
9030
|
}
|
|
8929
9031
|
}
|
|
8930
9032
|
}
|
|
@@ -9420,9 +9522,9 @@ async function evaluateWithJudgeTransport(request, options = {}) {
|
|
|
9420
9522
|
}
|
|
9421
9523
|
|
|
9422
9524
|
// src/core/capability/gate-shadow-ratchet.ts
|
|
9423
|
-
import { existsSync as
|
|
9424
|
-
import { mkdir as
|
|
9425
|
-
import
|
|
9525
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
9526
|
+
import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
|
|
9527
|
+
import path29 from "node:path";
|
|
9426
9528
|
var DEFAULT_STATE = {
|
|
9427
9529
|
version: 1,
|
|
9428
9530
|
policyJudgeComparisons: 0,
|
|
@@ -9431,15 +9533,15 @@ var DEFAULT_STATE = {
|
|
|
9431
9533
|
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
9432
9534
|
};
|
|
9433
9535
|
function ratchetPath(stateDir) {
|
|
9434
|
-
return
|
|
9536
|
+
return path29.join(stateDir, "gate-shadow-ratchet.json");
|
|
9435
9537
|
}
|
|
9436
9538
|
async function loadState(stateDir) {
|
|
9437
9539
|
const filePath = ratchetPath(stateDir);
|
|
9438
|
-
if (!
|
|
9540
|
+
if (!existsSync9(filePath)) {
|
|
9439
9541
|
return { ...DEFAULT_STATE, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9440
9542
|
}
|
|
9441
9543
|
try {
|
|
9442
|
-
const raw = JSON.parse(await
|
|
9544
|
+
const raw = JSON.parse(await readFile8(filePath, "utf8"));
|
|
9443
9545
|
return {
|
|
9444
9546
|
...DEFAULT_STATE,
|
|
9445
9547
|
...raw,
|
|
@@ -9450,7 +9552,7 @@ async function loadState(stateDir) {
|
|
|
9450
9552
|
}
|
|
9451
9553
|
}
|
|
9452
9554
|
async function saveState(stateDir, state) {
|
|
9453
|
-
await
|
|
9555
|
+
await mkdir9(stateDir, { recursive: true, mode: 448 });
|
|
9454
9556
|
await writeFile5(
|
|
9455
9557
|
ratchetPath(stateDir),
|
|
9456
9558
|
`${JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
@@ -9573,7 +9675,7 @@ function scheduleGateShadowAudit(params) {
|
|
|
9573
9675
|
async function runGatePolicyShadowComparison(params) {
|
|
9574
9676
|
const judge = params.config.judge;
|
|
9575
9677
|
const runtime = normalizeJudgeRuntimeConfig(judge.runtime);
|
|
9576
|
-
const stateDir = params.stateDir ?? belayStateDir(params.config,
|
|
9678
|
+
const stateDir = params.stateDir ?? belayStateDir(params.config, path30.join(params.repoRoot, ".belay"));
|
|
9577
9679
|
const transport = await evaluateWithJudgeTransport(
|
|
9578
9680
|
{
|
|
9579
9681
|
prompt: buildTier1Prompt(params.command),
|
|
@@ -9863,15 +9965,15 @@ async function loadClassifierAuthorization(params) {
|
|
|
9863
9965
|
}
|
|
9864
9966
|
|
|
9865
9967
|
// src/core/capability/allowlist.ts
|
|
9866
|
-
import { existsSync as
|
|
9968
|
+
import { existsSync as existsSync10, readFileSync as readFileSync3 } from "node:fs";
|
|
9867
9969
|
init_config();
|
|
9868
9970
|
init_path_utils();
|
|
9869
|
-
import
|
|
9971
|
+
import path31 from "node:path";
|
|
9870
9972
|
function fsScopeAllowlistPath(config, repoLocalStateDir) {
|
|
9871
|
-
return
|
|
9973
|
+
return path31.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
|
|
9872
9974
|
}
|
|
9873
9975
|
function loadFsScopeAllowlistSync(filePath) {
|
|
9874
|
-
if (!
|
|
9976
|
+
if (!existsSync10(filePath)) {
|
|
9875
9977
|
return { version: 1, paths: [] };
|
|
9876
9978
|
}
|
|
9877
9979
|
const raw = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
@@ -9963,7 +10065,7 @@ function checkGatedActionLimits(action) {
|
|
|
9963
10065
|
// src/core/capability/paths.ts
|
|
9964
10066
|
init_path_utils();
|
|
9965
10067
|
init_shell_tokenizer();
|
|
9966
|
-
import
|
|
10068
|
+
import path32 from "node:path";
|
|
9967
10069
|
function applyPatchTargets(patch) {
|
|
9968
10070
|
const targets = [];
|
|
9969
10071
|
for (const line of patch.split("\n")) {
|
|
@@ -10028,7 +10130,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
|
|
|
10028
10130
|
const paths = /* @__PURE__ */ new Set();
|
|
10029
10131
|
const filePath = extractToolFilePath(payload);
|
|
10030
10132
|
if (filePath) {
|
|
10031
|
-
const resolved =
|
|
10133
|
+
const resolved = path32.isAbsolute(filePath) ? filePath : path32.resolve(cwd, filePath);
|
|
10032
10134
|
if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
|
|
10033
10135
|
paths.add(resolved);
|
|
10034
10136
|
}
|
|
@@ -10038,7 +10140,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
|
|
|
10038
10140
|
const patch = extractToolPatch(payload);
|
|
10039
10141
|
if (patch) {
|
|
10040
10142
|
for (const target of applyPatchTargets(patch)) {
|
|
10041
|
-
const resolved =
|
|
10143
|
+
const resolved = path32.isAbsolute(target) ? target : path32.resolve(cwd, target);
|
|
10042
10144
|
if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
|
|
10043
10145
|
paths.add(resolved);
|
|
10044
10146
|
}
|
|
@@ -10092,7 +10194,7 @@ function policyReasonToLegacyReason(decision) {
|
|
|
10092
10194
|
// src/core/capability/policy-engine.ts
|
|
10093
10195
|
init_git_resource_identity();
|
|
10094
10196
|
import { createHash as createHash10 } from "node:crypto";
|
|
10095
|
-
import
|
|
10197
|
+
import path33 from "node:path";
|
|
10096
10198
|
init_path_utils();
|
|
10097
10199
|
init_shell_tokenizer();
|
|
10098
10200
|
init_grant();
|
|
@@ -10384,7 +10486,7 @@ function hashSession(value) {
|
|
|
10384
10486
|
return createHash10("sha256").update(value).digest("hex").slice(0, 16);
|
|
10385
10487
|
}
|
|
10386
10488
|
function resolveCapabilityPath2(targetPath, cwd) {
|
|
10387
|
-
const joined =
|
|
10489
|
+
const joined = path33.isAbsolute(targetPath) ? targetPath : path33.join(cwd, targetPath);
|
|
10388
10490
|
return canonicalPath(joined);
|
|
10389
10491
|
}
|
|
10390
10492
|
function actionForFileMutation(analysis) {
|
|
@@ -10463,7 +10565,7 @@ function isRepoLocalPackageExec(request) {
|
|
|
10463
10565
|
return false;
|
|
10464
10566
|
}
|
|
10465
10567
|
const commandPath = canonicalPath(request.resource.command);
|
|
10466
|
-
return
|
|
10568
|
+
return path33.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
|
|
10467
10569
|
}
|
|
10468
10570
|
function isRepoLocalRoutineWrite(request, sensitivePaths) {
|
|
10469
10571
|
if (!isRepoLocalShellLocation(request)) {
|
|
@@ -10711,6 +10813,40 @@ function evaluateFileMutationPolicy(analysis, config, auth) {
|
|
|
10711
10813
|
);
|
|
10712
10814
|
return { request, decision };
|
|
10713
10815
|
}
|
|
10816
|
+
function buildFileReadCapabilityRequest(analysis) {
|
|
10817
|
+
return {
|
|
10818
|
+
version: CAPABILITY_REQUEST_VERSION,
|
|
10819
|
+
principal: {
|
|
10820
|
+
adapter: analysis.adapter,
|
|
10821
|
+
repoRoot: analysis.repoRoot,
|
|
10822
|
+
sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`)
|
|
10823
|
+
},
|
|
10824
|
+
action: "fs.read",
|
|
10825
|
+
resource: { kind: "path", path: analysis.resolvedPath },
|
|
10826
|
+
context: {
|
|
10827
|
+
cwd: analysis.cwd,
|
|
10828
|
+
inputFingerprint: analysis.inputFingerprint,
|
|
10829
|
+
hookKind: analysis.hookKind,
|
|
10830
|
+
analysisBasis: [`tool:${analysis.toolKind}`, `location:${analysis.locationLabel}`, "read"]
|
|
10831
|
+
},
|
|
10832
|
+
evidence: {
|
|
10833
|
+
level: analysis.locationLabel === "repo_local" ? "certain" : "possible",
|
|
10834
|
+
signals: [...analysis.signals, "effect.fs_read"]
|
|
10835
|
+
}
|
|
10836
|
+
};
|
|
10837
|
+
}
|
|
10838
|
+
function evaluateFileReadPolicy(analysis, config, auth) {
|
|
10839
|
+
const request = buildFileReadCapabilityRequest(analysis);
|
|
10840
|
+
const enriched = enrichAuthWithMaterializedGrants(request, config, {
|
|
10841
|
+
...auth,
|
|
10842
|
+
sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths
|
|
10843
|
+
});
|
|
10844
|
+
const decision = getDefaultPolicyEngine().evaluate(
|
|
10845
|
+
request,
|
|
10846
|
+
buildAuthorizationContext(config, analysis.trustedWorkspaceRoots, enriched)
|
|
10847
|
+
);
|
|
10848
|
+
return { request, decision };
|
|
10849
|
+
}
|
|
10714
10850
|
|
|
10715
10851
|
// src/core/capability/reasons.ts
|
|
10716
10852
|
var FS_SCOPE_REASONS = /* @__PURE__ */ new Set(["outside_repo_mutation", "outside_repo_redirect"]);
|
|
@@ -10727,15 +10863,15 @@ function shouldSkipBrokerApprovedRecord(brokerActive, approvalReason) {
|
|
|
10727
10863
|
}
|
|
10728
10864
|
|
|
10729
10865
|
// src/core/capability/trusted-workspace-roots.ts
|
|
10730
|
-
import { existsSync as
|
|
10866
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
10731
10867
|
init_config();
|
|
10732
10868
|
init_path_utils();
|
|
10733
|
-
import
|
|
10869
|
+
import path34 from "node:path";
|
|
10734
10870
|
function trustedWorkspaceRootsPath(config, repoLocalStateDir) {
|
|
10735
|
-
return
|
|
10871
|
+
return path34.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
|
|
10736
10872
|
}
|
|
10737
10873
|
function loadTrustedWorkspaceRootsSync(filePath) {
|
|
10738
|
-
if (!
|
|
10874
|
+
if (!existsSync11(filePath)) {
|
|
10739
10875
|
return { version: 1, roots: [] };
|
|
10740
10876
|
}
|
|
10741
10877
|
const raw = JSON.parse(readFileSync4(filePath, "utf8"));
|
|
@@ -10761,7 +10897,7 @@ function sanitizeTrustedWorkspaceRootEntries(input) {
|
|
|
10761
10897
|
const source = record.source === "approval" ? "approval" : void 0;
|
|
10762
10898
|
return [
|
|
10763
10899
|
{
|
|
10764
|
-
path:
|
|
10900
|
+
path: path34.resolve(record.path),
|
|
10765
10901
|
approvedAt,
|
|
10766
10902
|
approvalId,
|
|
10767
10903
|
...source ? { source } : {}
|
|
@@ -10790,7 +10926,7 @@ function isDirectoryPath(targetPath) {
|
|
|
10790
10926
|
function isBroadTrustedWorkspaceRoot(targetPath) {
|
|
10791
10927
|
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
10792
10928
|
const root = normalizeTrustedWorkspaceRootPath(targetPath);
|
|
10793
|
-
if (root === normalizeTrustedWorkspaceRootPath(
|
|
10929
|
+
if (root === normalizeTrustedWorkspaceRootPath(path34.parse(root).root)) {
|
|
10794
10930
|
return true;
|
|
10795
10931
|
}
|
|
10796
10932
|
if (home && normalizeTrustedWorkspaceRootPath(home) === root) {
|
|
@@ -10814,7 +10950,7 @@ function isHighStakesTrustedWorkspaceRoot(targetPath) {
|
|
|
10814
10950
|
return false;
|
|
10815
10951
|
}
|
|
10816
10952
|
return HOME_HIGH_STAKES_SEGMENTS.some(
|
|
10817
|
-
(segment) => pathWithinRoot(
|
|
10953
|
+
(segment) => pathWithinRoot(path34.join(homeRoot, segment), normalized)
|
|
10818
10954
|
);
|
|
10819
10955
|
}
|
|
10820
10956
|
function validateTrustedWorkspaceRootCandidate(params) {
|
|
@@ -10851,7 +10987,7 @@ function validateTrustedWorkspaceRootCandidate(params) {
|
|
|
10851
10987
|
init_config_layers();
|
|
10852
10988
|
|
|
10853
10989
|
// src/core/contained-execution/eligibility.ts
|
|
10854
|
-
import
|
|
10990
|
+
import path35 from "node:path";
|
|
10855
10991
|
init_path_utils();
|
|
10856
10992
|
var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
|
|
10857
10993
|
"command_substitution",
|
|
@@ -10868,7 +11004,7 @@ var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
|
|
|
10868
11004
|
]);
|
|
10869
11005
|
function isContainedUnknownExecutionEligible(config, action, result) {
|
|
10870
11006
|
const contained = config.sandbox.containedExecution;
|
|
10871
|
-
if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !
|
|
11007
|
+
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) {
|
|
10872
11008
|
return false;
|
|
10873
11009
|
}
|
|
10874
11010
|
const plan = result.effectPlan;
|
|
@@ -11148,7 +11284,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
|
|
|
11148
11284
|
}
|
|
11149
11285
|
|
|
11150
11286
|
// src/core/classify-tool.ts
|
|
11151
|
-
import
|
|
11287
|
+
import path50 from "node:path";
|
|
11152
11288
|
init_fingerprint2();
|
|
11153
11289
|
init_path_utils();
|
|
11154
11290
|
init_scrub();
|
|
@@ -11268,13 +11404,13 @@ function worstEffectDecision(decisions) {
|
|
|
11268
11404
|
|
|
11269
11405
|
// src/core/effect-ir/shell-lower.ts
|
|
11270
11406
|
init_shell_tokenizer();
|
|
11271
|
-
import
|
|
11407
|
+
import path49 from "node:path";
|
|
11272
11408
|
|
|
11273
11409
|
// src/core/verdict/docker-compose-run.ts
|
|
11274
|
-
import
|
|
11410
|
+
import path37 from "node:path";
|
|
11275
11411
|
|
|
11276
11412
|
// src/core/verdict/recursive-invocation.ts
|
|
11277
|
-
import
|
|
11413
|
+
import path36 from "node:path";
|
|
11278
11414
|
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
|
|
11279
11415
|
var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
|
|
11280
11416
|
var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
|
|
@@ -11336,7 +11472,7 @@ var OSASCRIPT_PROFILE = {
|
|
|
11336
11472
|
attachedValuePrefixes: []
|
|
11337
11473
|
};
|
|
11338
11474
|
function normalizeInterpreter(value) {
|
|
11339
|
-
return
|
|
11475
|
+
return path36.basename(value);
|
|
11340
11476
|
}
|
|
11341
11477
|
function scriptResult(interpreter, token) {
|
|
11342
11478
|
if (!token) {
|
|
@@ -11566,7 +11702,7 @@ function parseOptions(words, start, options) {
|
|
|
11566
11702
|
function decodeDockerComposeRun(tokens) {
|
|
11567
11703
|
if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
|
|
11568
11704
|
const words = tokens.filter((token) => token.kind === "word");
|
|
11569
|
-
const head =
|
|
11705
|
+
const head = path37.basename(words[0]?.value ?? "");
|
|
11570
11706
|
let index;
|
|
11571
11707
|
if (head === "docker-compose") {
|
|
11572
11708
|
index = 1;
|
|
@@ -11607,7 +11743,7 @@ function decodeDockerComposeRun(tokens) {
|
|
|
11607
11743
|
}
|
|
11608
11744
|
|
|
11609
11745
|
// src/core/verdict/egress-classify.ts
|
|
11610
|
-
import
|
|
11746
|
+
import path38 from "node:path";
|
|
11611
11747
|
var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
|
|
11612
11748
|
"aws",
|
|
11613
11749
|
"curl",
|
|
@@ -11656,7 +11792,7 @@ function isEgressToolHead(head) {
|
|
|
11656
11792
|
return EGRESS_TOOL_HEADS.has(head);
|
|
11657
11793
|
}
|
|
11658
11794
|
function decodeEgressEffects(params) {
|
|
11659
|
-
const head =
|
|
11795
|
+
const head = path38.basename(params.tokens[0] ?? "");
|
|
11660
11796
|
if (head !== "curl" && head !== "wget" && head !== "gh") {
|
|
11661
11797
|
return null;
|
|
11662
11798
|
}
|
|
@@ -11664,7 +11800,7 @@ function decodeEgressEffects(params) {
|
|
|
11664
11800
|
const provenance = { segment: params.segment };
|
|
11665
11801
|
const requirements = [];
|
|
11666
11802
|
for (const file of decoded.files) {
|
|
11667
|
-
const resolved =
|
|
11803
|
+
const resolved = path38.resolve(params.cwd, expandHome2(file));
|
|
11668
11804
|
requirements.push(
|
|
11669
11805
|
requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
|
|
11670
11806
|
"egress.explicit_file_read"
|
|
@@ -11686,7 +11822,7 @@ function decodeEgressEffects(params) {
|
|
|
11686
11822
|
if (file === "-") {
|
|
11687
11823
|
continue;
|
|
11688
11824
|
}
|
|
11689
|
-
const resolved =
|
|
11825
|
+
const resolved = path38.resolve(params.cwd, expandHome2(file));
|
|
11690
11826
|
if (resolved === "/dev/null") {
|
|
11691
11827
|
continue;
|
|
11692
11828
|
}
|
|
@@ -12164,13 +12300,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
|
|
|
12164
12300
|
if (head === "wget" && !explicitOutput) {
|
|
12165
12301
|
outputFiles.push(
|
|
12166
12302
|
...endpointOutputNames.map(
|
|
12167
|
-
(name) => outputDirectory ?
|
|
12303
|
+
(name) => outputDirectory ? path38.join(outputDirectory, name) : name
|
|
12168
12304
|
)
|
|
12169
12305
|
);
|
|
12170
12306
|
} else if (head === "curl" && remoteNameOutput) {
|
|
12171
12307
|
outputFiles.push(
|
|
12172
12308
|
...endpointOutputNames.map(
|
|
12173
|
-
(name) => outputDirectory ?
|
|
12309
|
+
(name) => outputDirectory ? path38.join(outputDirectory, name) : name
|
|
12174
12310
|
)
|
|
12175
12311
|
);
|
|
12176
12312
|
}
|
|
@@ -12184,7 +12320,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
|
|
|
12184
12320
|
outputFiles: [
|
|
12185
12321
|
...new Set(
|
|
12186
12322
|
outputFiles.map(
|
|
12187
|
-
(file) => outputDirectory && directoryEligibleOutputs.has(file) && !
|
|
12323
|
+
(file) => outputDirectory && directoryEligibleOutputs.has(file) && !path38.isAbsolute(file) ? path38.join(outputDirectory, file) : file
|
|
12188
12324
|
)
|
|
12189
12325
|
)
|
|
12190
12326
|
],
|
|
@@ -12199,7 +12335,7 @@ function remoteOutputName(spec) {
|
|
|
12199
12335
|
} catch {
|
|
12200
12336
|
pathname = spec.split(/[?#]/, 1)[0] ?? "";
|
|
12201
12337
|
}
|
|
12202
|
-
const name =
|
|
12338
|
+
const name = path38.posix.basename(pathname);
|
|
12203
12339
|
return name && name !== "/" ? name : "index.html";
|
|
12204
12340
|
}
|
|
12205
12341
|
function decodeGhGrammar(tokens) {
|
|
@@ -12364,7 +12500,7 @@ function expandHome2(value) {
|
|
|
12364
12500
|
return process.env.HOME ?? value;
|
|
12365
12501
|
}
|
|
12366
12502
|
if (value.startsWith("~/")) {
|
|
12367
|
-
return
|
|
12503
|
+
return path38.join(process.env.HOME ?? "~", value.slice(2));
|
|
12368
12504
|
}
|
|
12369
12505
|
return value;
|
|
12370
12506
|
}
|
|
@@ -12383,7 +12519,7 @@ function requirement(tag, action, resource, segment, signals) {
|
|
|
12383
12519
|
}
|
|
12384
12520
|
|
|
12385
12521
|
// src/core/verdict/git-classifier.ts
|
|
12386
|
-
import
|
|
12522
|
+
import path39 from "node:path";
|
|
12387
12523
|
init_shell_tokenizer();
|
|
12388
12524
|
var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
|
|
12389
12525
|
"--copy",
|
|
@@ -12479,7 +12615,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
12479
12615
|
var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
|
|
12480
12616
|
var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
|
|
12481
12617
|
function isGitExecutable(token) {
|
|
12482
|
-
return
|
|
12618
|
+
return path39.basename(token) === "git";
|
|
12483
12619
|
}
|
|
12484
12620
|
function takesValue(flag) {
|
|
12485
12621
|
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=");
|
|
@@ -12507,7 +12643,7 @@ function peelGlobalOptions(tokens, baseCwd) {
|
|
|
12507
12643
|
if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
|
|
12508
12644
|
const value = tokens[index + 1];
|
|
12509
12645
|
if (token === "-C" && value) {
|
|
12510
|
-
effectiveCwd =
|
|
12646
|
+
effectiveCwd = path39.resolve(baseCwd, value);
|
|
12511
12647
|
} else if (token === "--work-tree" && value) {
|
|
12512
12648
|
workTree = value;
|
|
12513
12649
|
} else if (token === "--git-dir" && value) {
|
|
@@ -12517,7 +12653,7 @@ function peelGlobalOptions(tokens, baseCwd) {
|
|
|
12517
12653
|
continue;
|
|
12518
12654
|
}
|
|
12519
12655
|
if (token.startsWith("-C") && token.length > 2) {
|
|
12520
|
-
effectiveCwd =
|
|
12656
|
+
effectiveCwd = path39.resolve(baseCwd, token.slice(2));
|
|
12521
12657
|
index += 1;
|
|
12522
12658
|
continue;
|
|
12523
12659
|
}
|
|
@@ -12660,7 +12796,7 @@ function looksLikeDiffPathOperand(token) {
|
|
|
12660
12796
|
if (!looksLikeFileOperand(token)) {
|
|
12661
12797
|
return false;
|
|
12662
12798
|
}
|
|
12663
|
-
if (token.startsWith(".") ||
|
|
12799
|
+
if (token.startsWith(".") || path39.isAbsolute(token)) {
|
|
12664
12800
|
return true;
|
|
12665
12801
|
}
|
|
12666
12802
|
return token.includes(".");
|
|
@@ -12668,12 +12804,12 @@ function looksLikeDiffPathOperand(token) {
|
|
|
12668
12804
|
function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
|
|
12669
12805
|
const resolveBase = effectiveCwd ?? baseCwd;
|
|
12670
12806
|
if (workTree) {
|
|
12671
|
-
return
|
|
12807
|
+
return path39.resolve(resolveBase, workTree);
|
|
12672
12808
|
}
|
|
12673
12809
|
if (gitDir) {
|
|
12674
|
-
const resolvedGitDir =
|
|
12675
|
-
if (
|
|
12676
|
-
return
|
|
12810
|
+
const resolvedGitDir = path39.resolve(resolveBase, gitDir);
|
|
12811
|
+
if (path39.basename(resolvedGitDir) === ".git") {
|
|
12812
|
+
return path39.dirname(resolvedGitDir);
|
|
12677
12813
|
}
|
|
12678
12814
|
}
|
|
12679
12815
|
return void 0;
|
|
@@ -12754,7 +12890,7 @@ function classifyGitCommand(tokens, baseCwd) {
|
|
|
12754
12890
|
const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
|
|
12755
12891
|
const normalizedKey = `git ${subcommand}`;
|
|
12756
12892
|
const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
|
|
12757
|
-
const effectiveGitDir = gitDir ?
|
|
12893
|
+
const effectiveGitDir = gitDir ? path39.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
|
|
12758
12894
|
const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
|
|
12759
12895
|
(target, index, targets) => Boolean(target) && targets.indexOf(target) === index
|
|
12760
12896
|
);
|
|
@@ -12906,9 +13042,9 @@ function decodeGitEffects(params) {
|
|
|
12906
13042
|
...subcommand === "push" ? ["tier0_external"] : []
|
|
12907
13043
|
];
|
|
12908
13044
|
const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
|
|
12909
|
-
const workTreeRoot = normalized.workTree ?
|
|
12910
|
-
const gitRefRoot = normalized.gitDir ?
|
|
12911
|
-
const gitControlRoot = normalized.gitDir ? gitRefRoot :
|
|
13045
|
+
const workTreeRoot = normalized.workTree ? path39.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
|
|
13046
|
+
const gitRefRoot = normalized.gitDir ? path39.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
|
|
13047
|
+
const gitControlRoot = normalized.gitDir ? gitRefRoot : path39.join(gitRefRoot, ".git");
|
|
12912
13048
|
const requirements = [];
|
|
12913
13049
|
if (subcommand === "fetch" || subcommand === "pull") {
|
|
12914
13050
|
const positionals = gitRemotePositionals(args);
|
|
@@ -13067,7 +13203,7 @@ function decodeGitEffects(params) {
|
|
|
13067
13203
|
gitRequirement(
|
|
13068
13204
|
"control_plane.write",
|
|
13069
13205
|
"control_plane.write",
|
|
13070
|
-
{ kind: "path", path:
|
|
13206
|
+
{ kind: "path", path: path39.join(gitControlRoot, "logs") },
|
|
13071
13207
|
params.segment,
|
|
13072
13208
|
[...signals, "git_history_destructive", "git.reflog.mutate"]
|
|
13073
13209
|
)
|
|
@@ -13125,7 +13261,7 @@ function decodeGitEffects(params) {
|
|
|
13125
13261
|
gitRequirement(
|
|
13126
13262
|
"fs.read",
|
|
13127
13263
|
"fs.read",
|
|
13128
|
-
{ kind: "path", path:
|
|
13264
|
+
{ kind: "path", path: path39.resolve(workTreeRoot, operand) },
|
|
13129
13265
|
params.segment,
|
|
13130
13266
|
[...signals, "git.path.read"]
|
|
13131
13267
|
)
|
|
@@ -13160,7 +13296,7 @@ function decodeGitEffects(params) {
|
|
|
13160
13296
|
gitRequirement(
|
|
13161
13297
|
"fs.write",
|
|
13162
13298
|
"fs.write",
|
|
13163
|
-
{ kind: "path", path:
|
|
13299
|
+
{ kind: "path", path: path39.resolve(workTreeRoot, operand) },
|
|
13164
13300
|
params.segment,
|
|
13165
13301
|
[...signals, "git.path.write"]
|
|
13166
13302
|
)
|
|
@@ -13344,8 +13480,8 @@ function gitRequirement(tag, action, resource, segment, signals) {
|
|
|
13344
13480
|
}
|
|
13345
13481
|
|
|
13346
13482
|
// src/core/verdict/launcher-resolve.ts
|
|
13347
|
-
import { existsSync as
|
|
13348
|
-
import
|
|
13483
|
+
import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
|
|
13484
|
+
import path40 from "node:path";
|
|
13349
13485
|
|
|
13350
13486
|
// src/core/verdict/makefile-expand.ts
|
|
13351
13487
|
var MAX_EXPAND_DEPTH = 16;
|
|
@@ -13518,8 +13654,8 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
13518
13654
|
"why"
|
|
13519
13655
|
]);
|
|
13520
13656
|
function readPackageJson(dir) {
|
|
13521
|
-
const packagePath =
|
|
13522
|
-
if (!
|
|
13657
|
+
const packagePath = path40.join(dir, "package.json");
|
|
13658
|
+
if (!existsSync12(packagePath)) {
|
|
13523
13659
|
return null;
|
|
13524
13660
|
}
|
|
13525
13661
|
try {
|
|
@@ -13529,17 +13665,17 @@ function readPackageJson(dir) {
|
|
|
13529
13665
|
}
|
|
13530
13666
|
}
|
|
13531
13667
|
function findPackageJson(startDir, stopDir) {
|
|
13532
|
-
let current =
|
|
13533
|
-
const stop =
|
|
13668
|
+
let current = path40.resolve(startDir);
|
|
13669
|
+
const stop = path40.resolve(stopDir);
|
|
13534
13670
|
while (true) {
|
|
13535
|
-
const packagePath =
|
|
13536
|
-
if (
|
|
13671
|
+
const packagePath = path40.join(current, "package.json");
|
|
13672
|
+
if (existsSync12(packagePath)) {
|
|
13537
13673
|
return packagePath;
|
|
13538
13674
|
}
|
|
13539
|
-
if (current === stop || current ===
|
|
13540
|
-
return
|
|
13675
|
+
if (current === stop || current === path40.dirname(current)) {
|
|
13676
|
+
return existsSync12(packagePath) ? packagePath : null;
|
|
13541
13677
|
}
|
|
13542
|
-
const parent =
|
|
13678
|
+
const parent = path40.dirname(current);
|
|
13543
13679
|
if (!parent.startsWith(stop) && parent !== current) {
|
|
13544
13680
|
}
|
|
13545
13681
|
if (parent === current) {
|
|
@@ -13596,7 +13732,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
|
|
|
13596
13732
|
}
|
|
13597
13733
|
return { recipes: [], opaque: true, reason: "package_json_missing" };
|
|
13598
13734
|
}
|
|
13599
|
-
const pkg = readPackageJson(
|
|
13735
|
+
const pkg = readPackageJson(path40.dirname(packagePath));
|
|
13600
13736
|
const scripts = pkg?.scripts;
|
|
13601
13737
|
if (!scripts || typeof scripts !== "object") {
|
|
13602
13738
|
return { recipes: [], opaque: true, reason: "package_scripts_missing" };
|
|
@@ -13687,20 +13823,20 @@ function parseMakefileRecipeContent(content) {
|
|
|
13687
13823
|
function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
13688
13824
|
const candidates = ["Makefile", "makefile", "GNUmakefile"];
|
|
13689
13825
|
let makefilePath = null;
|
|
13690
|
-
let searchDir =
|
|
13691
|
-
const stop =
|
|
13826
|
+
let searchDir = path40.resolve(cwd);
|
|
13827
|
+
const stop = path40.resolve(repoRoot);
|
|
13692
13828
|
while (true) {
|
|
13693
13829
|
for (const name of candidates) {
|
|
13694
|
-
const candidate =
|
|
13695
|
-
if (
|
|
13830
|
+
const candidate = path40.join(searchDir, name);
|
|
13831
|
+
if (existsSync12(candidate)) {
|
|
13696
13832
|
makefilePath = candidate;
|
|
13697
13833
|
break;
|
|
13698
13834
|
}
|
|
13699
13835
|
}
|
|
13700
|
-
if (makefilePath || searchDir === stop || searchDir ===
|
|
13836
|
+
if (makefilePath || searchDir === stop || searchDir === path40.dirname(searchDir)) {
|
|
13701
13837
|
break;
|
|
13702
13838
|
}
|
|
13703
|
-
searchDir =
|
|
13839
|
+
searchDir = path40.dirname(searchDir);
|
|
13704
13840
|
}
|
|
13705
13841
|
if (!makefilePath) {
|
|
13706
13842
|
return { recipes: [], opaque: true, reason: "unknown_local_effect" };
|
|
@@ -13727,7 +13863,7 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
|
13727
13863
|
}
|
|
13728
13864
|
const entry = targets.get(name);
|
|
13729
13865
|
if (!entry) {
|
|
13730
|
-
if (!
|
|
13866
|
+
if (!existsSync12(path40.resolve(path40.dirname(makefilePath), name))) {
|
|
13731
13867
|
hasUndefinedPrerequisite = true;
|
|
13732
13868
|
}
|
|
13733
13869
|
return;
|
|
@@ -13826,7 +13962,7 @@ function resolveLauncherRecipe(params) {
|
|
|
13826
13962
|
}
|
|
13827
13963
|
|
|
13828
13964
|
// src/core/verdict/parser.ts
|
|
13829
|
-
import
|
|
13965
|
+
import path41 from "node:path";
|
|
13830
13966
|
|
|
13831
13967
|
// src/core/shell-substitution.ts
|
|
13832
13968
|
function findStructuralCommandSubstitutions(command) {
|
|
@@ -14066,7 +14202,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
14066
14202
|
".sh"
|
|
14067
14203
|
]);
|
|
14068
14204
|
function normalizeHead(token) {
|
|
14069
|
-
const base =
|
|
14205
|
+
const base = path41.basename(token);
|
|
14070
14206
|
if (base && base !== "." && base !== "..") {
|
|
14071
14207
|
return base;
|
|
14072
14208
|
}
|
|
@@ -14376,7 +14512,7 @@ function isBareInterpreter(tokens) {
|
|
|
14376
14512
|
return false;
|
|
14377
14513
|
}
|
|
14378
14514
|
const scriptArg = args.find((token) => !token.startsWith("-"));
|
|
14379
|
-
if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(
|
|
14515
|
+
if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path41.extname(scriptArg))) {
|
|
14380
14516
|
return false;
|
|
14381
14517
|
}
|
|
14382
14518
|
if (scriptArg) {
|
|
@@ -14616,7 +14752,7 @@ function mergeNodes(nodes) {
|
|
|
14616
14752
|
}
|
|
14617
14753
|
|
|
14618
14754
|
// src/core/effect-ir/shell-lower/argv-delegate-gate.ts
|
|
14619
|
-
import
|
|
14755
|
+
import path42 from "node:path";
|
|
14620
14756
|
var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
|
|
14621
14757
|
"sudo",
|
|
14622
14758
|
"env",
|
|
@@ -14637,7 +14773,7 @@ function shouldApplyArgvDelegate(head, innerTokens, depth) {
|
|
|
14637
14773
|
if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
|
|
14638
14774
|
return false;
|
|
14639
14775
|
}
|
|
14640
|
-
const innerHead =
|
|
14776
|
+
const innerHead = path42.basename(innerTokens[0] ?? "");
|
|
14641
14777
|
if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
|
|
14642
14778
|
return false;
|
|
14643
14779
|
}
|
|
@@ -14744,7 +14880,7 @@ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegm
|
|
|
14744
14880
|
|
|
14745
14881
|
// src/core/effect-ir/shell-lower/tokens.ts
|
|
14746
14882
|
init_shell_tokenizer();
|
|
14747
|
-
import
|
|
14883
|
+
import path43 from "node:path";
|
|
14748
14884
|
var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
14749
14885
|
var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
|
|
14750
14886
|
var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
|
|
@@ -14888,9 +15024,9 @@ function resolvePathOperand(operand, cwd) {
|
|
|
14888
15024
|
return process.env.HOME ?? operand;
|
|
14889
15025
|
}
|
|
14890
15026
|
if (operand.startsWith("~/")) {
|
|
14891
|
-
return
|
|
15027
|
+
return path43.join(process.env.HOME ?? "~", operand.slice(2));
|
|
14892
15028
|
}
|
|
14893
|
-
return
|
|
15029
|
+
return path43.resolve(cwd, operand);
|
|
14894
15030
|
}
|
|
14895
15031
|
function isOptionToken(value) {
|
|
14896
15032
|
return value.startsWith("-") || value.startsWith("+");
|
|
@@ -14902,7 +15038,7 @@ function pipeToShell(command) {
|
|
|
14902
15038
|
return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
|
|
14903
15039
|
}
|
|
14904
15040
|
function executableBaseName(head) {
|
|
14905
|
-
return
|
|
15041
|
+
return path43.basename(head);
|
|
14906
15042
|
}
|
|
14907
15043
|
function isMetadataOnlyArgv(argv) {
|
|
14908
15044
|
return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
|
|
@@ -15021,15 +15157,22 @@ function effectChangingEnvironmentSignals(head, env, changedNames) {
|
|
|
15021
15157
|
}
|
|
15022
15158
|
|
|
15023
15159
|
// src/core/effect-ir/shell-lower/decode-process.ts
|
|
15024
|
-
import
|
|
15160
|
+
import path47 from "node:path";
|
|
15025
15161
|
|
|
15026
15162
|
// src/core/effect-ir/shell-lower/decoders/belay.ts
|
|
15027
|
-
import
|
|
15163
|
+
import path44 from "node:path";
|
|
15028
15164
|
function decodeBelay(args, repoRoot, segment) {
|
|
15029
15165
|
const [section, operation, key] = args;
|
|
15030
15166
|
const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
|
|
15031
15167
|
const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
|
|
15032
15168
|
const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
|
|
15169
|
+
const approvalAuthorityCommand = [
|
|
15170
|
+
"approval-token",
|
|
15171
|
+
"approve",
|
|
15172
|
+
"revoke",
|
|
15173
|
+
"standing-allow"
|
|
15174
|
+
].includes(section ?? "");
|
|
15175
|
+
const configTrustMutation = section === "config" && operation === "trust";
|
|
15033
15176
|
if (judgeCommand || configRead || configJudgeMutation) {
|
|
15034
15177
|
return [
|
|
15035
15178
|
processRequirement("belay", "inspect", segment, [
|
|
@@ -15038,12 +15181,23 @@ function decodeBelay(args, repoRoot, segment) {
|
|
|
15038
15181
|
])
|
|
15039
15182
|
];
|
|
15040
15183
|
}
|
|
15184
|
+
if (approvalAuthorityCommand || configTrustMutation) {
|
|
15185
|
+
return [
|
|
15186
|
+
requirement2(
|
|
15187
|
+
"control_plane.write",
|
|
15188
|
+
"control_plane.write",
|
|
15189
|
+
{ kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
|
|
15190
|
+
segment,
|
|
15191
|
+
[approvalAuthorityCommand ? "belay.approval_authority" : "belay.config_trust"]
|
|
15192
|
+
)
|
|
15193
|
+
];
|
|
15194
|
+
}
|
|
15041
15195
|
if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
|
|
15042
15196
|
return [
|
|
15043
15197
|
requirement2(
|
|
15044
15198
|
"control_plane.write",
|
|
15045
15199
|
"control_plane.write",
|
|
15046
|
-
{ kind: "path", path:
|
|
15200
|
+
{ kind: "path", path: path44.join(repoRoot, ".belay-control-plane") },
|
|
15047
15201
|
segment,
|
|
15048
15202
|
["belay.config_non_judge_mutation"]
|
|
15049
15203
|
)
|
|
@@ -15284,7 +15438,7 @@ function validDockerInfo(args) {
|
|
|
15284
15438
|
// src/core/effect-ir/shell-lower/decoders/filesystem.ts
|
|
15285
15439
|
init_git_resource_identity();
|
|
15286
15440
|
import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
|
|
15287
|
-
import
|
|
15441
|
+
import path45 from "node:path";
|
|
15288
15442
|
function decodeCopyMove(head, args, cwd, segment) {
|
|
15289
15443
|
const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
|
|
15290
15444
|
const operands = [];
|
|
@@ -15412,11 +15566,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
|
|
|
15412
15566
|
function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
|
|
15413
15567
|
try {
|
|
15414
15568
|
if (finalOperandIsSymlink) {
|
|
15415
|
-
return
|
|
15569
|
+
return path45.join(realpathSync4.native(path45.dirname(targetPath)), path45.basename(targetPath));
|
|
15416
15570
|
}
|
|
15417
15571
|
return realpathSync4.native(targetPath);
|
|
15418
15572
|
} catch {
|
|
15419
|
-
return
|
|
15573
|
+
return path45.resolve(targetPath);
|
|
15420
15574
|
}
|
|
15421
15575
|
}
|
|
15422
15576
|
function isSymbolicLink(targetPath) {
|
|
@@ -15427,8 +15581,8 @@ function isSymbolicLink(targetPath) {
|
|
|
15427
15581
|
}
|
|
15428
15582
|
}
|
|
15429
15583
|
function pathContains(ancestor, candidate) {
|
|
15430
|
-
const relative =
|
|
15431
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
15584
|
+
const relative = path45.relative(path45.resolve(ancestor), path45.resolve(candidate));
|
|
15585
|
+
return relative === "" || !relative.startsWith("..") && !path45.isAbsolute(relative);
|
|
15432
15586
|
}
|
|
15433
15587
|
function filesystemReadOperands(head, args) {
|
|
15434
15588
|
switch (head) {
|
|
@@ -15568,7 +15722,7 @@ function decodePrisma(args, env, repoRoot, segment) {
|
|
|
15568
15722
|
|
|
15569
15723
|
// src/core/effect-ir/shell-lower/decoders/ruby.ts
|
|
15570
15724
|
init_path_utils();
|
|
15571
|
-
import
|
|
15725
|
+
import path46 from "node:path";
|
|
15572
15726
|
var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
|
|
15573
15727
|
function railsReadOnlySubcommand(args) {
|
|
15574
15728
|
const subcommand = args[0];
|
|
@@ -15578,7 +15732,7 @@ function railsReadOnlySubcommand(args) {
|
|
|
15578
15732
|
return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
|
|
15579
15733
|
}
|
|
15580
15734
|
function isRubyTestScript(scriptPath) {
|
|
15581
|
-
const base =
|
|
15735
|
+
const base = path46.basename(scriptPath);
|
|
15582
15736
|
return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
|
|
15583
15737
|
}
|
|
15584
15738
|
function parseRubyTestInvocation(args) {
|
|
@@ -15992,7 +16146,7 @@ function decodeProcessOrFilesystem(params) {
|
|
|
15992
16146
|
requirement2(
|
|
15993
16147
|
"fs.read",
|
|
15994
16148
|
"fs.read",
|
|
15995
|
-
{ kind: "path", path:
|
|
16149
|
+
{ kind: "path", path: path47.resolve(cwd, syntax) },
|
|
15996
16150
|
segment,
|
|
15997
16151
|
["shell.syntax_source_read"]
|
|
15998
16152
|
)
|
|
@@ -16198,7 +16352,7 @@ function packageExecInnerIsMetadata(peel) {
|
|
|
16198
16352
|
|
|
16199
16353
|
// src/core/effect-ir/shell-lower/segment.ts
|
|
16200
16354
|
init_shell_tokenizer();
|
|
16201
|
-
import
|
|
16355
|
+
import path48 from "node:path";
|
|
16202
16356
|
var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
|
|
16203
16357
|
var SHELL_GLOB_PATTERN = /[*?[]/;
|
|
16204
16358
|
function requiresKnownCwd(requirementValue) {
|
|
@@ -16213,11 +16367,11 @@ function joinNestedOpacity(outer, nested) {
|
|
|
16213
16367
|
}
|
|
16214
16368
|
function startsLocalPostgresService(command) {
|
|
16215
16369
|
const tokens = tokenizeShell(command);
|
|
16216
|
-
return
|
|
16370
|
+
return path48.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
|
|
16217
16371
|
}
|
|
16218
16372
|
function resolveCdTransition(command, currentCwd) {
|
|
16219
16373
|
const tokens = tokenizeShell(command);
|
|
16220
|
-
if (
|
|
16374
|
+
if (path48.basename(tokens[0] ?? "") !== "cd") {
|
|
16221
16375
|
return null;
|
|
16222
16376
|
}
|
|
16223
16377
|
const target = tokens[1] ?? "~";
|
|
@@ -16380,7 +16534,7 @@ function lowerSegment(command, context) {
|
|
|
16380
16534
|
stripStructuredRedirects(lexed.tokens),
|
|
16381
16535
|
stripRedirects(parsedTokens)
|
|
16382
16536
|
);
|
|
16383
|
-
const head =
|
|
16537
|
+
const head = path49.basename(tokens[0] ?? parsed.head);
|
|
16384
16538
|
let opacity = segmentOpacity(command);
|
|
16385
16539
|
const signals = /* @__PURE__ */ new Set();
|
|
16386
16540
|
const requirements = [];
|
|
@@ -16963,17 +17117,41 @@ function verdictToClassifyResult(result, config, options = {}) {
|
|
|
16963
17117
|
|
|
16964
17118
|
// src/core/classify-tool.ts
|
|
16965
17119
|
var DEFAULT_SENSITIVE_PATHS = [".env", ".env.*", "**/credentials/**"];
|
|
16966
|
-
|
|
16967
|
-
|
|
16968
|
-
"
|
|
16969
|
-
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
17120
|
+
function toolInputRecord(payload) {
|
|
17121
|
+
const toolInput = payload.tool_input;
|
|
17122
|
+
if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
|
|
17123
|
+
return null;
|
|
17124
|
+
}
|
|
17125
|
+
return toolInput;
|
|
17126
|
+
}
|
|
17127
|
+
function hasMutationFields(input) {
|
|
17128
|
+
return typeof input.contents === "string" || typeof input.old_string === "string" || typeof input.new_string === "string" || typeof input.newContents === "string";
|
|
17129
|
+
}
|
|
17130
|
+
function inferToolPayloadEffect(payload, toolName) {
|
|
17131
|
+
if (extractShellCommand(payload)) {
|
|
17132
|
+
return "shell";
|
|
17133
|
+
}
|
|
17134
|
+
const input = toolInputRecord(payload);
|
|
17135
|
+
if (!input) {
|
|
17136
|
+
return "indeterminate";
|
|
17137
|
+
}
|
|
17138
|
+
if (hasMutationFields(input)) {
|
|
17139
|
+
return "file_mutation";
|
|
17140
|
+
}
|
|
17141
|
+
if (extractPatch(payload)) {
|
|
17142
|
+
return "apply_patch";
|
|
17143
|
+
}
|
|
17144
|
+
if (typeof input.pattern === "string" || typeof input.glob_pattern === "string") {
|
|
17145
|
+
return "search_read";
|
|
17146
|
+
}
|
|
17147
|
+
if (typeof input.file_path === "string") {
|
|
17148
|
+
return "file_read";
|
|
17149
|
+
}
|
|
17150
|
+
if (typeof input.path === "string" || typeof input.target_file === "string") {
|
|
17151
|
+
return normalizedToolName(toolName) === "delete" ? "file_delete" : "indeterminate";
|
|
17152
|
+
}
|
|
17153
|
+
return "indeterminate";
|
|
17154
|
+
}
|
|
16977
17155
|
function policyAuth(options) {
|
|
16978
17156
|
if (!options.grants && options.attestation === void 0 && options.egressProxyActive === void 0) {
|
|
16979
17157
|
return void 0;
|
|
@@ -17153,58 +17331,367 @@ function classifyFileMutationWithPolicy(params) {
|
|
|
17153
17331
|
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17154
17332
|
};
|
|
17155
17333
|
}
|
|
17156
|
-
|
|
17157
|
-
const
|
|
17158
|
-
const
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
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
|
-
|
|
17334
|
+
function classifyFileReadWithPolicy(params) {
|
|
17335
|
+
const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
|
|
17336
|
+
const { request, decision } = evaluateFileReadPolicy(
|
|
17337
|
+
{
|
|
17338
|
+
hookKind: "tool",
|
|
17339
|
+
toolKind: params.toolKind,
|
|
17340
|
+
filePath: params.filePath,
|
|
17341
|
+
resolvedPath: params.resolvedPath,
|
|
17342
|
+
repoRoot: params.repoRoot,
|
|
17343
|
+
cwd: params.cwd,
|
|
17344
|
+
inputFingerprint: fingerprint,
|
|
17345
|
+
signals: params.signals,
|
|
17346
|
+
locationLabel: params.locationLabel,
|
|
17347
|
+
trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
|
|
17348
|
+
sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths
|
|
17349
|
+
},
|
|
17350
|
+
params.config,
|
|
17351
|
+
policyAuth(params.options)
|
|
17352
|
+
);
|
|
17353
|
+
if (policyDecisionRequiresAsk(decision)) {
|
|
17354
|
+
return {
|
|
17355
|
+
verdict: "deny_pending_approval",
|
|
17356
|
+
reason: policyReasonToLegacyReason(decision),
|
|
17357
|
+
summary: params.filePath,
|
|
17358
|
+
fingerprint,
|
|
17359
|
+
assessment: {
|
|
17360
|
+
reversibility: "reversible",
|
|
17361
|
+
external: params.locationLabel === "outside_repo",
|
|
17362
|
+
blastRadius: params.locationLabel === "outside_repo" ? "outside the repository" : "sensitive repository file",
|
|
17363
|
+
confidence: 0.85,
|
|
17364
|
+
signals: [...params.signals, ...decision.signals]
|
|
17365
|
+
},
|
|
17366
|
+
capabilityRequests: [request],
|
|
17367
|
+
authorizationDecision: decision,
|
|
17368
|
+
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17369
|
+
};
|
|
17370
|
+
}
|
|
17371
|
+
return {
|
|
17372
|
+
verdict: "allow",
|
|
17373
|
+
reason: "effect.fs_read",
|
|
17374
|
+
summary: params.filePath,
|
|
17375
|
+
fingerprint,
|
|
17376
|
+
assessment: {
|
|
17377
|
+
reversibility: "reversible",
|
|
17378
|
+
external: false,
|
|
17379
|
+
blastRadius: "tool scope",
|
|
17380
|
+
confidence: 0.88,
|
|
17381
|
+
signals: [...params.signals, ...decision.signals]
|
|
17382
|
+
},
|
|
17383
|
+
capabilityRequests: [request],
|
|
17384
|
+
authorizationDecision: decision,
|
|
17385
|
+
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17386
|
+
};
|
|
17387
|
+
}
|
|
17388
|
+
function classifyFilePathMutation(params) {
|
|
17389
|
+
const {
|
|
17390
|
+
toolName,
|
|
17391
|
+
toolKind,
|
|
17392
|
+
filePath,
|
|
17393
|
+
repoRoot,
|
|
17394
|
+
cwd,
|
|
17395
|
+
config,
|
|
17396
|
+
options,
|
|
17397
|
+
sensitivePaths,
|
|
17398
|
+
protectedRoots,
|
|
17399
|
+
isDelete
|
|
17400
|
+
} = params;
|
|
17401
|
+
const signals = [];
|
|
17402
|
+
const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
|
|
17403
|
+
const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
|
|
17404
|
+
if (hitsProtectedRoot) {
|
|
17405
|
+
signals.push("control_plane_path");
|
|
17406
|
+
return classifyFileMutationWithPolicy({
|
|
17407
|
+
toolName,
|
|
17408
|
+
toolKind,
|
|
17409
|
+
filePath,
|
|
17410
|
+
resolvedPath,
|
|
17411
|
+
repoRoot,
|
|
17412
|
+
cwd,
|
|
17413
|
+
config,
|
|
17414
|
+
options,
|
|
17415
|
+
signals,
|
|
17416
|
+
isDelete,
|
|
17417
|
+
locationLabel: "control_plane"
|
|
17418
|
+
});
|
|
17419
|
+
}
|
|
17420
|
+
const workspaceMatch = resolveWorkspaceRootMatch(
|
|
17421
|
+
repoRoot,
|
|
17422
|
+
options.trustedWorkspaceRoots,
|
|
17423
|
+
resolvedPath
|
|
17424
|
+
);
|
|
17425
|
+
if (workspaceMatch === null) {
|
|
17426
|
+
signals.push("outside_repo_path");
|
|
17427
|
+
return classifyFileMutationWithPolicy({
|
|
17428
|
+
toolName,
|
|
17429
|
+
toolKind,
|
|
17430
|
+
filePath,
|
|
17431
|
+
resolvedPath,
|
|
17432
|
+
repoRoot,
|
|
17433
|
+
cwd,
|
|
17434
|
+
config,
|
|
17435
|
+
options,
|
|
17436
|
+
signals,
|
|
17437
|
+
isDelete,
|
|
17438
|
+
locationLabel: "outside_repo"
|
|
17439
|
+
});
|
|
17440
|
+
}
|
|
17441
|
+
if (workspaceMatch.kind === "trusted") {
|
|
17442
|
+
signals.push("trusted_workspace_root");
|
|
17443
|
+
}
|
|
17444
|
+
const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
|
|
17445
|
+
const workspacePrescan = mutationPrescanRequiresAsk({
|
|
17446
|
+
targets: [filePath],
|
|
17447
|
+
cwd,
|
|
17448
|
+
repoRoot,
|
|
17449
|
+
trustedCwd,
|
|
17450
|
+
trustedWorkspaceRoots: options.trustedWorkspaceRoots,
|
|
17451
|
+
sensitivePaths
|
|
17452
|
+
});
|
|
17453
|
+
if (workspacePrescan) {
|
|
17454
|
+
return classifyFileMutationWithPolicy({
|
|
17455
|
+
toolName,
|
|
17456
|
+
toolKind,
|
|
17457
|
+
filePath,
|
|
17458
|
+
resolvedPath,
|
|
17459
|
+
repoRoot,
|
|
17460
|
+
cwd,
|
|
17461
|
+
config,
|
|
17462
|
+
options,
|
|
17463
|
+
signals,
|
|
17464
|
+
isDelete,
|
|
17465
|
+
locationLabel: "sensitive_path"
|
|
17466
|
+
});
|
|
17467
|
+
}
|
|
17468
|
+
const relativePath = workspaceMatch.relativePath;
|
|
17469
|
+
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
17470
|
+
signals.push("sensitive_path");
|
|
17471
|
+
return classifyFileMutationWithPolicy({
|
|
17472
|
+
toolName,
|
|
17473
|
+
toolKind,
|
|
17474
|
+
filePath,
|
|
17475
|
+
resolvedPath,
|
|
17476
|
+
repoRoot,
|
|
17477
|
+
cwd,
|
|
17478
|
+
config,
|
|
17479
|
+
options,
|
|
17480
|
+
signals,
|
|
17481
|
+
isDelete,
|
|
17482
|
+
locationLabel: "sensitive_path"
|
|
17483
|
+
});
|
|
17484
|
+
}
|
|
17485
|
+
if (isDelete) {
|
|
17486
|
+
if (isGitPath(resolvedPath, repoRoot)) {
|
|
17487
|
+
signals.push("protected_artifact");
|
|
17488
|
+
return classifyFileMutationWithPolicy({
|
|
17489
|
+
toolName,
|
|
17490
|
+
toolKind,
|
|
17491
|
+
filePath,
|
|
17492
|
+
resolvedPath,
|
|
17493
|
+
repoRoot,
|
|
17494
|
+
cwd,
|
|
17495
|
+
config,
|
|
17496
|
+
options,
|
|
17497
|
+
signals,
|
|
17498
|
+
isDelete: true,
|
|
17499
|
+
locationLabel: "sensitive_path"
|
|
17500
|
+
});
|
|
17501
|
+
}
|
|
17502
|
+
signals.push("file_delete");
|
|
17503
|
+
return classifyFileMutationWithPolicy({
|
|
17504
|
+
toolName,
|
|
17505
|
+
toolKind,
|
|
17506
|
+
filePath,
|
|
17507
|
+
resolvedPath,
|
|
17508
|
+
repoRoot,
|
|
17509
|
+
cwd,
|
|
17510
|
+
config,
|
|
17511
|
+
options,
|
|
17512
|
+
signals,
|
|
17513
|
+
isDelete: true,
|
|
17514
|
+
locationLabel: "repo_local"
|
|
17515
|
+
});
|
|
17516
|
+
}
|
|
17517
|
+
signals.push("file_mutation");
|
|
17518
|
+
return classifyFileMutationWithPolicy({
|
|
17519
|
+
toolName,
|
|
17520
|
+
toolKind,
|
|
17521
|
+
filePath,
|
|
17522
|
+
resolvedPath,
|
|
17523
|
+
repoRoot,
|
|
17524
|
+
cwd,
|
|
17525
|
+
config,
|
|
17526
|
+
options,
|
|
17527
|
+
signals,
|
|
17528
|
+
isDelete: false,
|
|
17529
|
+
locationLabel: "repo_local"
|
|
17530
|
+
});
|
|
17531
|
+
}
|
|
17532
|
+
function classifyFilePathRead(params) {
|
|
17533
|
+
const {
|
|
17534
|
+
toolName,
|
|
17535
|
+
toolKind,
|
|
17536
|
+
filePath,
|
|
17537
|
+
repoRoot,
|
|
17538
|
+
cwd,
|
|
17539
|
+
config,
|
|
17540
|
+
options,
|
|
17541
|
+
sensitivePaths,
|
|
17542
|
+
protectedRoots
|
|
17543
|
+
} = params;
|
|
17544
|
+
const signals = ["effect.fs_read"];
|
|
17545
|
+
const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
|
|
17546
|
+
if (protectedRoots.some((root) => pathWithinRoot(root, resolvedPath))) {
|
|
17547
|
+
signals.push("control_plane_path");
|
|
17548
|
+
return classifyFileReadWithPolicy({
|
|
17549
|
+
toolName,
|
|
17550
|
+
toolKind,
|
|
17551
|
+
filePath,
|
|
17552
|
+
resolvedPath,
|
|
17553
|
+
repoRoot,
|
|
17554
|
+
cwd,
|
|
17555
|
+
config,
|
|
17556
|
+
options,
|
|
17557
|
+
signals,
|
|
17558
|
+
locationLabel: "control_plane"
|
|
17559
|
+
});
|
|
17560
|
+
}
|
|
17561
|
+
const workspaceMatch = resolveWorkspaceRootMatch(
|
|
17562
|
+
repoRoot,
|
|
17563
|
+
options.trustedWorkspaceRoots,
|
|
17564
|
+
resolvedPath
|
|
17565
|
+
);
|
|
17566
|
+
if (workspaceMatch === null) {
|
|
17567
|
+
signals.push("outside_repo_path");
|
|
17568
|
+
return classifyFileReadWithPolicy({
|
|
17569
|
+
toolName,
|
|
17570
|
+
toolKind,
|
|
17571
|
+
filePath,
|
|
17572
|
+
resolvedPath,
|
|
17573
|
+
repoRoot,
|
|
17574
|
+
cwd,
|
|
17575
|
+
config,
|
|
17576
|
+
options,
|
|
17577
|
+
signals,
|
|
17578
|
+
locationLabel: "outside_repo"
|
|
17579
|
+
});
|
|
17580
|
+
}
|
|
17581
|
+
const relativePath = workspaceMatch.relativePath;
|
|
17582
|
+
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
17583
|
+
signals.push("sensitive_path");
|
|
17584
|
+
return classifyFileReadWithPolicy({
|
|
17585
|
+
toolName,
|
|
17586
|
+
toolKind,
|
|
17587
|
+
filePath,
|
|
17588
|
+
resolvedPath,
|
|
17589
|
+
repoRoot,
|
|
17590
|
+
cwd,
|
|
17591
|
+
config,
|
|
17592
|
+
options,
|
|
17593
|
+
signals,
|
|
17594
|
+
locationLabel: "sensitive_path"
|
|
17595
|
+
});
|
|
17596
|
+
}
|
|
17597
|
+
return classifyFileReadWithPolicy({
|
|
17598
|
+
toolName,
|
|
17599
|
+
toolKind,
|
|
17600
|
+
filePath,
|
|
17601
|
+
resolvedPath,
|
|
17602
|
+
repoRoot,
|
|
17603
|
+
cwd,
|
|
17604
|
+
config,
|
|
17605
|
+
options,
|
|
17606
|
+
signals,
|
|
17607
|
+
locationLabel: "repo_local"
|
|
17608
|
+
});
|
|
17609
|
+
}
|
|
17610
|
+
function indeterminateToolResult(toolName, payload, repoRoot, options) {
|
|
17611
|
+
const summary = canonicalStringify(scrubPayload(payload.tool_input ?? {}, options));
|
|
17612
|
+
const fingerprint = toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot);
|
|
17613
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17614
|
+
return {
|
|
17615
|
+
verdict: "deny_pending_approval",
|
|
17616
|
+
reason: "indeterminate_tool_effect",
|
|
17617
|
+
summary,
|
|
17618
|
+
fingerprint,
|
|
17619
|
+
assessment: {
|
|
17620
|
+
reversibility: "irreversible",
|
|
17621
|
+
external: false,
|
|
17622
|
+
blastRadius: "unknown tool action",
|
|
17623
|
+
confidence: 0.5,
|
|
17624
|
+
signals: ["indeterminate_tool_effect"]
|
|
17625
|
+
}
|
|
17626
|
+
};
|
|
17627
|
+
}
|
|
17628
|
+
return {
|
|
17629
|
+
verdict: "allow_flagged",
|
|
17630
|
+
reason: "indeterminate_tool_effect",
|
|
17631
|
+
summary,
|
|
17632
|
+
fingerprint,
|
|
17633
|
+
assessment: {
|
|
17634
|
+
reversibility: "recoverable_with_cost",
|
|
17635
|
+
external: false,
|
|
17636
|
+
blastRadius: "tool scope",
|
|
17637
|
+
confidence: 0.5,
|
|
17638
|
+
signals: ["indeterminate_tool_effect"]
|
|
17639
|
+
}
|
|
17640
|
+
};
|
|
17641
|
+
}
|
|
17642
|
+
async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
17643
|
+
const toolName = String(payload.tool_name ?? "");
|
|
17644
|
+
const toolKind = normalizedToolName(toolName);
|
|
17645
|
+
const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
|
|
17646
|
+
const protectedRoots = [
|
|
17647
|
+
...options.protectedArtifactRoots ?? [],
|
|
17648
|
+
...options.controlPlaneDir ? [options.controlPlaneDir] : []
|
|
17649
|
+
];
|
|
17650
|
+
if (toolKind === "shell") {
|
|
17651
|
+
const command = extractShellCommand(payload);
|
|
17652
|
+
if (!command) {
|
|
17653
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17654
|
+
return {
|
|
17655
|
+
verdict: "deny_pending_approval",
|
|
17656
|
+
reason: "tool_shell_missing_command",
|
|
17657
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17658
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17659
|
+
assessment: {
|
|
17660
|
+
reversibility: "irreversible",
|
|
17661
|
+
external: false,
|
|
17662
|
+
blastRadius: "tool shell",
|
|
17663
|
+
confidence: 0.85,
|
|
17664
|
+
signals: ["missing_command"]
|
|
17665
|
+
}
|
|
17666
|
+
};
|
|
17667
|
+
}
|
|
17668
|
+
return {
|
|
17669
|
+
verdict: "allow_flagged",
|
|
17670
|
+
reason: "tool_shell_missing_command",
|
|
17671
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17672
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17673
|
+
assessment: {
|
|
17674
|
+
reversibility: "recoverable_with_cost",
|
|
17675
|
+
external: false,
|
|
17676
|
+
blastRadius: "tool shell",
|
|
17677
|
+
confidence: 0.5,
|
|
17678
|
+
signals: ["missing_command"]
|
|
17679
|
+
}
|
|
17680
|
+
};
|
|
17681
|
+
}
|
|
17682
|
+
const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
|
|
17683
|
+
return {
|
|
17684
|
+
...shellResult,
|
|
17685
|
+
summary: command
|
|
17686
|
+
};
|
|
17687
|
+
}
|
|
17688
|
+
const effect = inferToolPayloadEffect(payload, toolName);
|
|
17689
|
+
if (effect === "file_mutation" || effect === "file_delete") {
|
|
17690
|
+
const filePath2 = extractFilePath(payload);
|
|
17691
|
+
if (!filePath2) {
|
|
17692
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17693
|
+
return {
|
|
17694
|
+
verdict: "deny_pending_approval",
|
|
17208
17695
|
reason: "file_mutation_missing_path",
|
|
17209
17696
|
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17210
17697
|
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
@@ -17231,138 +17718,20 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17231
17718
|
}
|
|
17232
17719
|
};
|
|
17233
17720
|
}
|
|
17234
|
-
|
|
17235
|
-
const resolvedPath = path49.isAbsolute(filePath) ? filePath : path49.resolve(cwd, filePath);
|
|
17236
|
-
const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
|
|
17237
|
-
if (hitsProtectedRoot) {
|
|
17238
|
-
signals.push("control_plane_path");
|
|
17239
|
-
return classifyFileMutationWithPolicy({
|
|
17240
|
-
toolName,
|
|
17241
|
-
toolKind,
|
|
17242
|
-
filePath,
|
|
17243
|
-
resolvedPath,
|
|
17244
|
-
repoRoot,
|
|
17245
|
-
cwd,
|
|
17246
|
-
config,
|
|
17247
|
-
options,
|
|
17248
|
-
signals,
|
|
17249
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17250
|
-
locationLabel: "control_plane"
|
|
17251
|
-
});
|
|
17252
|
-
}
|
|
17253
|
-
const workspaceMatch = resolveWorkspaceRootMatch(
|
|
17254
|
-
repoRoot,
|
|
17255
|
-
options.trustedWorkspaceRoots,
|
|
17256
|
-
resolvedPath
|
|
17257
|
-
);
|
|
17258
|
-
if (workspaceMatch === null) {
|
|
17259
|
-
signals.push("outside_repo_path");
|
|
17260
|
-
return classifyFileMutationWithPolicy({
|
|
17261
|
-
toolName,
|
|
17262
|
-
toolKind,
|
|
17263
|
-
filePath,
|
|
17264
|
-
resolvedPath,
|
|
17265
|
-
repoRoot,
|
|
17266
|
-
cwd,
|
|
17267
|
-
config,
|
|
17268
|
-
options,
|
|
17269
|
-
signals,
|
|
17270
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17271
|
-
locationLabel: "outside_repo"
|
|
17272
|
-
});
|
|
17273
|
-
}
|
|
17274
|
-
if (workspaceMatch.kind === "trusted") {
|
|
17275
|
-
signals.push("trusted_workspace_root");
|
|
17276
|
-
}
|
|
17277
|
-
const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
|
|
17278
|
-
const workspacePrescan = mutationPrescanRequiresAsk({
|
|
17279
|
-
targets: [filePath],
|
|
17280
|
-
cwd,
|
|
17281
|
-
repoRoot,
|
|
17282
|
-
trustedCwd,
|
|
17283
|
-
trustedWorkspaceRoots: options.trustedWorkspaceRoots,
|
|
17284
|
-
sensitivePaths
|
|
17285
|
-
});
|
|
17286
|
-
if (workspacePrescan) {
|
|
17287
|
-
return classifyFileMutationWithPolicy({
|
|
17288
|
-
toolName,
|
|
17289
|
-
toolKind,
|
|
17290
|
-
filePath,
|
|
17291
|
-
resolvedPath,
|
|
17292
|
-
repoRoot,
|
|
17293
|
-
cwd,
|
|
17294
|
-
config,
|
|
17295
|
-
options,
|
|
17296
|
-
signals,
|
|
17297
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17298
|
-
locationLabel: "sensitive_path"
|
|
17299
|
-
});
|
|
17300
|
-
}
|
|
17301
|
-
const relativePath = workspaceMatch.relativePath;
|
|
17302
|
-
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
17303
|
-
signals.push("sensitive_path");
|
|
17304
|
-
return classifyFileMutationWithPolicy({
|
|
17305
|
-
toolName,
|
|
17306
|
-
toolKind,
|
|
17307
|
-
filePath,
|
|
17308
|
-
resolvedPath,
|
|
17309
|
-
repoRoot,
|
|
17310
|
-
cwd,
|
|
17311
|
-
config,
|
|
17312
|
-
options,
|
|
17313
|
-
signals,
|
|
17314
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17315
|
-
locationLabel: "sensitive_path"
|
|
17316
|
-
});
|
|
17317
|
-
}
|
|
17318
|
-
if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
|
|
17319
|
-
if (isGitPath(resolvedPath, repoRoot)) {
|
|
17320
|
-
signals.push("protected_artifact");
|
|
17321
|
-
return classifyFileMutationWithPolicy({
|
|
17322
|
-
toolName,
|
|
17323
|
-
toolKind,
|
|
17324
|
-
filePath,
|
|
17325
|
-
resolvedPath,
|
|
17326
|
-
repoRoot,
|
|
17327
|
-
cwd,
|
|
17328
|
-
config,
|
|
17329
|
-
options,
|
|
17330
|
-
signals,
|
|
17331
|
-
isDelete: true,
|
|
17332
|
-
locationLabel: "sensitive_path"
|
|
17333
|
-
});
|
|
17334
|
-
}
|
|
17335
|
-
signals.push("file_delete");
|
|
17336
|
-
return classifyFileMutationWithPolicy({
|
|
17337
|
-
toolName,
|
|
17338
|
-
toolKind,
|
|
17339
|
-
filePath,
|
|
17340
|
-
resolvedPath,
|
|
17341
|
-
repoRoot,
|
|
17342
|
-
cwd,
|
|
17343
|
-
config,
|
|
17344
|
-
options,
|
|
17345
|
-
signals,
|
|
17346
|
-
isDelete: true,
|
|
17347
|
-
locationLabel: "repo_local"
|
|
17348
|
-
});
|
|
17349
|
-
}
|
|
17350
|
-
signals.push("file_mutation");
|
|
17351
|
-
return classifyFileMutationWithPolicy({
|
|
17721
|
+
return classifyFilePathMutation({
|
|
17352
17722
|
toolName,
|
|
17353
17723
|
toolKind,
|
|
17354
|
-
filePath,
|
|
17355
|
-
resolvedPath,
|
|
17724
|
+
filePath: filePath2,
|
|
17356
17725
|
repoRoot,
|
|
17357
17726
|
cwd,
|
|
17358
17727
|
config,
|
|
17359
17728
|
options,
|
|
17360
|
-
|
|
17361
|
-
|
|
17362
|
-
|
|
17729
|
+
sensitivePaths,
|
|
17730
|
+
protectedRoots,
|
|
17731
|
+
isDelete: effect === "file_delete"
|
|
17363
17732
|
});
|
|
17364
17733
|
}
|
|
17365
|
-
if (
|
|
17734
|
+
if (effect === "apply_patch") {
|
|
17366
17735
|
const patch = extractPatch(payload);
|
|
17367
17736
|
const targets = patch ? applyPatchTargets2(patch) : [];
|
|
17368
17737
|
if (targets.length === 0) {
|
|
@@ -17400,7 +17769,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17400
17769
|
const result = await classifyToolUse(
|
|
17401
17770
|
{
|
|
17402
17771
|
tool_name: target.delete ? "Delete" : "Write",
|
|
17403
|
-
tool_input: { path: target.path }
|
|
17772
|
+
tool_input: target.delete ? { path: target.path } : { path: target.path, contents: " " }
|
|
17404
17773
|
},
|
|
17405
17774
|
repoRoot,
|
|
17406
17775
|
cwd,
|
|
@@ -17426,19 +17795,54 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17426
17795
|
}
|
|
17427
17796
|
};
|
|
17428
17797
|
}
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17434
|
-
assessment: {
|
|
17435
|
-
reversibility: "reversible",
|
|
17436
|
-
external: false,
|
|
17437
|
-
blastRadius: "tool scope",
|
|
17438
|
-
confidence: 0.5,
|
|
17439
|
-
signals: ["unclassified_tool"]
|
|
17798
|
+
if (effect === "file_read") {
|
|
17799
|
+
const filePath2 = extractFilePath(payload);
|
|
17800
|
+
if (!filePath2) {
|
|
17801
|
+
return indeterminateToolResult(toolName, payload, repoRoot, options);
|
|
17440
17802
|
}
|
|
17441
|
-
|
|
17803
|
+
return classifyFilePathRead({
|
|
17804
|
+
toolName,
|
|
17805
|
+
toolKind,
|
|
17806
|
+
filePath: filePath2,
|
|
17807
|
+
repoRoot,
|
|
17808
|
+
cwd,
|
|
17809
|
+
config,
|
|
17810
|
+
options,
|
|
17811
|
+
sensitivePaths,
|
|
17812
|
+
protectedRoots
|
|
17813
|
+
});
|
|
17814
|
+
}
|
|
17815
|
+
if (effect === "search_read") {
|
|
17816
|
+
return {
|
|
17817
|
+
verdict: "allow",
|
|
17818
|
+
reason: "effect.search_read",
|
|
17819
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17820
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17821
|
+
assessment: {
|
|
17822
|
+
reversibility: "reversible",
|
|
17823
|
+
external: false,
|
|
17824
|
+
blastRadius: "tool scope",
|
|
17825
|
+
confidence: 0.88,
|
|
17826
|
+
signals: ["effect.search_read"]
|
|
17827
|
+
}
|
|
17828
|
+
};
|
|
17829
|
+
}
|
|
17830
|
+
const filePath = extractFilePath(payload);
|
|
17831
|
+
if (filePath) {
|
|
17832
|
+
return classifyFilePathMutation({
|
|
17833
|
+
toolName,
|
|
17834
|
+
toolKind,
|
|
17835
|
+
filePath,
|
|
17836
|
+
repoRoot,
|
|
17837
|
+
cwd,
|
|
17838
|
+
config,
|
|
17839
|
+
options,
|
|
17840
|
+
sensitivePaths,
|
|
17841
|
+
protectedRoots,
|
|
17842
|
+
isDelete: toolKind === "delete"
|
|
17843
|
+
});
|
|
17844
|
+
}
|
|
17845
|
+
return indeterminateToolResult(toolName, payload, repoRoot, options);
|
|
17442
17846
|
}
|
|
17443
17847
|
|
|
17444
17848
|
// src/core/gate-engine.ts
|
|
@@ -17773,15 +18177,8 @@ function gateEnabledForAction(config, action) {
|
|
|
17773
18177
|
if (action.kind === "subagent") {
|
|
17774
18178
|
return config.gates.subagent;
|
|
17775
18179
|
}
|
|
17776
|
-
|
|
17777
|
-
|
|
17778
|
-
return config.gates.toolShell;
|
|
17779
|
-
}
|
|
17780
|
-
if (toolName === "Write" || toolName === "StrReplace" || toolName === "Delete") {
|
|
17781
|
-
return config.gates.fileMutation;
|
|
17782
|
-
}
|
|
17783
|
-
if (toolName === "Task") {
|
|
17784
|
-
return config.gates.subagent;
|
|
18180
|
+
if (action.kind === "tool") {
|
|
18181
|
+
return config.gates.fileMutation || config.gates.toolShell;
|
|
17785
18182
|
}
|
|
17786
18183
|
return true;
|
|
17787
18184
|
}
|
|
@@ -17818,7 +18215,7 @@ function hashDecisionConfig(config) {
|
|
|
17818
18215
|
init_fingerprint2();
|
|
17819
18216
|
|
|
17820
18217
|
// src/version.ts
|
|
17821
|
-
var PACKAGE_VERSION = "0.
|
|
18218
|
+
var PACKAGE_VERSION = "0.10.1";
|
|
17822
18219
|
|
|
17823
18220
|
// src/runtime-provenance.ts
|
|
17824
18221
|
function resolveRuntimeArtifactHash(artifactHash) {
|
|
@@ -17891,56 +18288,56 @@ function capabilityRequestsBlockRecovery(requests) {
|
|
|
17891
18288
|
init_fingerprint2();
|
|
17892
18289
|
init_path_utils();
|
|
17893
18290
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
17894
|
-
import { existsSync as
|
|
17895
|
-
import { mkdir as
|
|
17896
|
-
import
|
|
18291
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
18292
|
+
import { mkdir as mkdir12, readdir as readdir3, readFile as readFile13, rename as rename4, rm as rm6 } from "node:fs/promises";
|
|
18293
|
+
import path55 from "node:path";
|
|
17897
18294
|
|
|
17898
18295
|
// src/core/recovery/artifact-store.ts
|
|
17899
18296
|
init_fingerprint2();
|
|
17900
18297
|
init_path_utils();
|
|
17901
18298
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
17902
|
-
import { existsSync as
|
|
17903
|
-
import { lstat as lstat7, mkdir as
|
|
17904
|
-
import
|
|
18299
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
18300
|
+
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";
|
|
18301
|
+
import path52 from "node:path";
|
|
17905
18302
|
|
|
17906
18303
|
// src/core/recovery/snapshot-node.ts
|
|
17907
18304
|
init_fingerprint2();
|
|
17908
18305
|
init_path_utils();
|
|
17909
18306
|
import { createHash as createHash12 } from "node:crypto";
|
|
17910
|
-
import { existsSync as
|
|
18307
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
17911
18308
|
import {
|
|
17912
|
-
chmod as
|
|
18309
|
+
chmod as chmod4,
|
|
17913
18310
|
copyFile as copyFile2,
|
|
17914
18311
|
lstat as lstat6,
|
|
17915
|
-
mkdir as
|
|
17916
|
-
open as
|
|
17917
|
-
readFile as
|
|
18312
|
+
mkdir as mkdir10,
|
|
18313
|
+
open as open5,
|
|
18314
|
+
readFile as readFile9,
|
|
17918
18315
|
readlink as readlink4,
|
|
17919
18316
|
rm as rm4,
|
|
17920
18317
|
rmdir as rmdir2,
|
|
17921
18318
|
symlink as symlink3,
|
|
17922
18319
|
writeFile as writeFile6
|
|
17923
18320
|
} from "node:fs/promises";
|
|
17924
|
-
import
|
|
18321
|
+
import path51 from "node:path";
|
|
17925
18322
|
var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
|
|
17926
18323
|
function validRecoveryRelativePath(relativePath) {
|
|
17927
|
-
if (!relativePath || relativePath.includes("\0") ||
|
|
17928
|
-
const normalized =
|
|
17929
|
-
return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${
|
|
18324
|
+
if (!relativePath || relativePath.includes("\0") || path51.isAbsolute(relativePath)) return false;
|
|
18325
|
+
const normalized = path51.normalize(relativePath);
|
|
18326
|
+
return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path51.sep}`);
|
|
17930
18327
|
}
|
|
17931
18328
|
async function assertRecoverySafeTarget(resourceRoot, relativePath) {
|
|
17932
18329
|
if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
|
|
17933
18330
|
const root = canonicalPath(resourceRoot);
|
|
17934
|
-
const target =
|
|
17935
|
-
const relative =
|
|
17936
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
18331
|
+
const target = path51.resolve(root, relativePath);
|
|
18332
|
+
const relative = path51.relative(root, target);
|
|
18333
|
+
if (relative === ".." || relative.startsWith(`..${path51.sep}`) || path51.isAbsolute(relative)) {
|
|
17937
18334
|
throw new Error("recovery_path_escape");
|
|
17938
18335
|
}
|
|
17939
18336
|
let current = root;
|
|
17940
|
-
const parentParts =
|
|
18337
|
+
const parentParts = path51.relative(root, path51.dirname(target)).split(path51.sep).filter(Boolean);
|
|
17941
18338
|
for (const part of parentParts) {
|
|
17942
|
-
current =
|
|
17943
|
-
if (!
|
|
18339
|
+
current = path51.join(current, part);
|
|
18340
|
+
if (!existsSync13(current)) break;
|
|
17944
18341
|
const info = await lstat6(current);
|
|
17945
18342
|
if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
|
|
17946
18343
|
if (!info.isDirectory()) break;
|
|
@@ -17948,7 +18345,7 @@ async function assertRecoverySafeTarget(resourceRoot, relativePath) {
|
|
|
17948
18345
|
return target;
|
|
17949
18346
|
}
|
|
17950
18347
|
async function fsyncPath(filePath) {
|
|
17951
|
-
const handle = await
|
|
18348
|
+
const handle = await open5(filePath, "r");
|
|
17952
18349
|
try {
|
|
17953
18350
|
await handle.sync();
|
|
17954
18351
|
} finally {
|
|
@@ -17990,13 +18387,13 @@ async function captureRecoverySnapshot(filePath, options) {
|
|
|
17990
18387
|
return { kind: "directory", mode, hash: recoveryDirectoryHash(mode) };
|
|
17991
18388
|
}
|
|
17992
18389
|
if (!info.isFile()) throw new Error(RECOVERY_UNSUPPORTED_FILE_KIND);
|
|
17993
|
-
const content = await
|
|
18390
|
+
const content = await readFile9(filePath);
|
|
17994
18391
|
const hash = createHash12("sha256").update(content).digest("hex");
|
|
17995
18392
|
let blob;
|
|
17996
18393
|
if (options?.blobDir) {
|
|
17997
|
-
await
|
|
17998
|
-
const blobPath =
|
|
17999
|
-
if (!
|
|
18394
|
+
await mkdir10(options.blobDir, { recursive: true, mode: 448 });
|
|
18395
|
+
const blobPath = path51.join(options.blobDir, hash);
|
|
18396
|
+
if (!existsSync13(blobPath)) {
|
|
18000
18397
|
await writeFile6(blobPath, content, { mode: 384 });
|
|
18001
18398
|
await fsyncPath(blobPath);
|
|
18002
18399
|
}
|
|
@@ -18050,7 +18447,7 @@ async function validateRecoverySnapshot(params) {
|
|
|
18050
18447
|
if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
|
|
18051
18448
|
let content;
|
|
18052
18449
|
try {
|
|
18053
|
-
content = await
|
|
18450
|
+
content = await readFile9(path51.join(params.artifactDir, record.blob));
|
|
18054
18451
|
} catch {
|
|
18055
18452
|
throw new Error(params.corruptReason);
|
|
18056
18453
|
}
|
|
@@ -18078,16 +18475,16 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
|
|
|
18078
18475
|
]);
|
|
18079
18476
|
var STAGING_STALE_MS = 5 * 6e4;
|
|
18080
18477
|
function checkpointsRoot(stateDir) {
|
|
18081
|
-
return
|
|
18478
|
+
return path52.join(stateDir, "recovery", "checkpoints");
|
|
18082
18479
|
}
|
|
18083
18480
|
function checkpointDir(stateDir, checkpointId) {
|
|
18084
18481
|
if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
|
|
18085
18482
|
throw new Error("invalid_recovery_checkpoint_id");
|
|
18086
18483
|
}
|
|
18087
|
-
return
|
|
18484
|
+
return path52.join(checkpointsRoot(stateDir), checkpointId);
|
|
18088
18485
|
}
|
|
18089
18486
|
async function fsyncPath2(filePath) {
|
|
18090
|
-
const handle = await
|
|
18487
|
+
const handle = await open6(filePath, "r");
|
|
18091
18488
|
try {
|
|
18092
18489
|
await handle.sync();
|
|
18093
18490
|
} finally {
|
|
@@ -18095,13 +18492,13 @@ async function fsyncPath2(filePath) {
|
|
|
18095
18492
|
}
|
|
18096
18493
|
}
|
|
18097
18494
|
async function atomicWriteJson(filePath, value) {
|
|
18098
|
-
await
|
|
18495
|
+
await mkdir11(path52.dirname(filePath), { recursive: true, mode: 448 });
|
|
18099
18496
|
const temporary = `${filePath}.tmp-${randomUUID4()}`;
|
|
18100
18497
|
await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
|
|
18101
18498
|
`, { mode: 384 });
|
|
18102
18499
|
await fsyncPath2(temporary);
|
|
18103
|
-
await
|
|
18104
|
-
await fsyncPath2(
|
|
18500
|
+
await rename3(temporary, filePath);
|
|
18501
|
+
await fsyncPath2(path52.dirname(filePath));
|
|
18105
18502
|
}
|
|
18106
18503
|
async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
|
|
18107
18504
|
const value = {
|
|
@@ -18111,13 +18508,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
|
|
|
18111
18508
|
manifestHash,
|
|
18112
18509
|
...detail ? { detail } : {}
|
|
18113
18510
|
};
|
|
18114
|
-
await atomicWriteJson(
|
|
18511
|
+
await atomicWriteJson(path52.join(artifactDir, "state.json"), value);
|
|
18115
18512
|
}
|
|
18116
18513
|
async function directorySize(root) {
|
|
18117
|
-
if (!
|
|
18514
|
+
if (!existsSync14(root)) return 0;
|
|
18118
18515
|
let total = 0;
|
|
18119
18516
|
for (const entry of await readdir2(root, { withFileTypes: true })) {
|
|
18120
|
-
const entryPath =
|
|
18517
|
+
const entryPath = path52.join(root, entry.name);
|
|
18121
18518
|
if (entry.isDirectory()) total += await directorySize(entryPath);
|
|
18122
18519
|
else total += (await lstat7(entryPath)).size;
|
|
18123
18520
|
}
|
|
@@ -18162,9 +18559,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18162
18559
|
let rawManifest;
|
|
18163
18560
|
let state;
|
|
18164
18561
|
try {
|
|
18165
|
-
rawManifest = JSON.parse(await
|
|
18562
|
+
rawManifest = JSON.parse(await readFile10(path52.join(artifactDir, "manifest.json"), "utf8"));
|
|
18166
18563
|
state = JSON.parse(
|
|
18167
|
-
await
|
|
18564
|
+
await readFile10(path52.join(artifactDir, "state.json"), "utf8")
|
|
18168
18565
|
);
|
|
18169
18566
|
} catch {
|
|
18170
18567
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
@@ -18180,10 +18577,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18180
18577
|
}
|
|
18181
18578
|
const entryPaths = /* @__PURE__ */ new Set();
|
|
18182
18579
|
for (const entry of manifest.entries) {
|
|
18183
|
-
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(
|
|
18580
|
+
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))) {
|
|
18184
18581
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
18185
18582
|
}
|
|
18186
|
-
entryPaths.add(
|
|
18583
|
+
entryPaths.add(path52.normalize(entry.path));
|
|
18187
18584
|
for (const [side, snapshot] of [
|
|
18188
18585
|
["before", entry.before],
|
|
18189
18586
|
["after", entry.after]
|
|
@@ -18197,9 +18594,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18197
18594
|
});
|
|
18198
18595
|
}
|
|
18199
18596
|
}
|
|
18200
|
-
const receiptPath =
|
|
18597
|
+
const receiptPath = path52.join(artifactDir, "receipt.json");
|
|
18201
18598
|
let receipt;
|
|
18202
|
-
if (["applied", "restoring", "restored", "conflict"].includes(state.state) ||
|
|
18599
|
+
if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync14(receiptPath)) {
|
|
18203
18600
|
receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
|
|
18204
18601
|
}
|
|
18205
18602
|
return { artifactDir, manifest, state, manifestHash, ...receipt ? { receipt } : {} };
|
|
@@ -18207,7 +18604,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18207
18604
|
async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
18208
18605
|
let rawReceipt;
|
|
18209
18606
|
try {
|
|
18210
|
-
rawReceipt = JSON.parse(await
|
|
18607
|
+
rawReceipt = JSON.parse(await readFile10(path52.join(artifactDir, "receipt.json"), "utf8"));
|
|
18211
18608
|
} catch {
|
|
18212
18609
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
18213
18610
|
}
|
|
@@ -18230,8 +18627,8 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
|
|
|
18230
18627
|
return receipt;
|
|
18231
18628
|
}
|
|
18232
18629
|
async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
18233
|
-
const receiptPath =
|
|
18234
|
-
if (
|
|
18630
|
+
const receiptPath = path52.join(artifactDir, "receipt.json");
|
|
18631
|
+
if (existsSync14(receiptPath)) {
|
|
18235
18632
|
return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
|
|
18236
18633
|
}
|
|
18237
18634
|
const receipt = {
|
|
@@ -18248,14 +18645,14 @@ async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
|
18248
18645
|
}
|
|
18249
18646
|
async function checkpointIds(stateDir) {
|
|
18250
18647
|
const root = checkpointsRoot(stateDir);
|
|
18251
|
-
if (!
|
|
18648
|
+
if (!existsSync14(root)) return [];
|
|
18252
18649
|
return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^cp_[a-f0-9]{24}$/.test(entry.name)).map((entry) => entry.name);
|
|
18253
18650
|
}
|
|
18254
18651
|
async function artifactRepoRoot(stateDir, checkpointId) {
|
|
18255
18652
|
const artifactDir = checkpointDir(stateDir, checkpointId);
|
|
18256
18653
|
try {
|
|
18257
18654
|
const manifest = JSON.parse(
|
|
18258
|
-
await
|
|
18655
|
+
await readFile10(path52.join(artifactDir, "manifest.json"), "utf8")
|
|
18259
18656
|
);
|
|
18260
18657
|
if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
|
|
18261
18658
|
return canonicalPath(manifest.repoRoot);
|
|
@@ -18263,7 +18660,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
|
|
|
18263
18660
|
} catch {
|
|
18264
18661
|
}
|
|
18265
18662
|
try {
|
|
18266
|
-
const owner = JSON.parse(await
|
|
18663
|
+
const owner = JSON.parse(await readFile10(path52.join(artifactDir, "owner.json"), "utf8"));
|
|
18267
18664
|
return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
|
|
18268
18665
|
} catch {
|
|
18269
18666
|
return null;
|
|
@@ -18279,14 +18676,14 @@ async function checkpointIdsForRepo(stateDir, repoRoot) {
|
|
|
18279
18676
|
}
|
|
18280
18677
|
async function cleanupOrphanedStaging(stateDir) {
|
|
18281
18678
|
const root = checkpointsRoot(stateDir);
|
|
18282
|
-
if (!
|
|
18679
|
+
if (!existsSync14(root)) return;
|
|
18283
18680
|
const now = Date.now();
|
|
18284
18681
|
for (const entry of await readdir2(root, { withFileTypes: true })) {
|
|
18285
18682
|
if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
|
|
18286
|
-
const stagingPath =
|
|
18683
|
+
const stagingPath = path52.join(root, entry.name);
|
|
18287
18684
|
let stale = false;
|
|
18288
18685
|
try {
|
|
18289
|
-
const owner = JSON.parse(await
|
|
18686
|
+
const owner = JSON.parse(await readFile10(path52.join(stagingPath, "owner.json"), "utf8"));
|
|
18290
18687
|
const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
|
|
18291
18688
|
const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
|
|
18292
18689
|
let alive = false;
|
|
@@ -18326,9 +18723,9 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
|
|
|
18326
18723
|
|
|
18327
18724
|
// src/core/recovery/reconcile.ts
|
|
18328
18725
|
init_fingerprint2();
|
|
18329
|
-
import { existsSync as
|
|
18330
|
-
import { readFile as
|
|
18331
|
-
import
|
|
18726
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
18727
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
18728
|
+
import path53 from "node:path";
|
|
18332
18729
|
async function matchRecoverySide(resourceRoot, entries, side) {
|
|
18333
18730
|
for (const entry of entries) {
|
|
18334
18731
|
const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
|
|
@@ -18342,9 +18739,9 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
|
|
|
18342
18739
|
loaded = await readRecoveryArtifact(stateDir, checkpointId);
|
|
18343
18740
|
} catch {
|
|
18344
18741
|
const artifactDir = checkpointDir(stateDir, checkpointId);
|
|
18345
|
-
if (
|
|
18346
|
-
const manifestPath =
|
|
18347
|
-
const hash =
|
|
18742
|
+
if (existsSync15(artifactDir)) {
|
|
18743
|
+
const manifestPath = path53.join(artifactDir, "manifest.json");
|
|
18744
|
+
const hash = existsSync15(manifestPath) ? hashValue(await readFile11(manifestPath, "utf8")) : "unavailable";
|
|
18348
18745
|
await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
|
|
18349
18746
|
}
|
|
18350
18747
|
return "corrupt";
|
|
@@ -18379,8 +18776,8 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
|
|
|
18379
18776
|
|
|
18380
18777
|
// src/core/recovery/resource-identity.ts
|
|
18381
18778
|
init_fingerprint2();
|
|
18382
|
-
import { lstat as lstat8, readFile as
|
|
18383
|
-
import
|
|
18779
|
+
import { lstat as lstat8, readFile as readFile12, realpath as realpath3 } from "node:fs/promises";
|
|
18780
|
+
import path54 from "node:path";
|
|
18384
18781
|
async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
|
|
18385
18782
|
const resolvedRoot = await realpath3(resourceRoot);
|
|
18386
18783
|
if (resourceKind === "directory") {
|
|
@@ -18388,13 +18785,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
|
|
|
18388
18785
|
if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
|
|
18389
18786
|
return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
|
|
18390
18787
|
}
|
|
18391
|
-
const dotGit =
|
|
18788
|
+
const dotGit = path54.join(resolvedRoot, ".git");
|
|
18392
18789
|
const gitInfo = await lstat8(dotGit);
|
|
18393
18790
|
let gitMetadataPath = dotGit;
|
|
18394
18791
|
if (gitInfo.isFile()) {
|
|
18395
|
-
const marker = (await
|
|
18792
|
+
const marker = (await readFile12(dotGit, "utf8")).trim();
|
|
18396
18793
|
if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
|
|
18397
|
-
gitMetadataPath =
|
|
18794
|
+
gitMetadataPath = path54.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
|
|
18398
18795
|
} else if (!gitInfo.isDirectory()) {
|
|
18399
18796
|
throw new Error("recovery_repo_identity_unavailable");
|
|
18400
18797
|
}
|
|
@@ -18459,7 +18856,7 @@ async function garbageCollect(stateDir, config, repoRoot) {
|
|
|
18459
18856
|
async function prepareRecoveryCheckpoint(params) {
|
|
18460
18857
|
const backend = params.backend ?? "git_worktree";
|
|
18461
18858
|
const resourceKind = await resolveRecoveryResourceKind(backend, params.repoRoot);
|
|
18462
|
-
await
|
|
18859
|
+
await mkdir12(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
|
|
18463
18860
|
await cleanupOrphanedStaging(params.stateDir);
|
|
18464
18861
|
await garbageCollect(params.stateDir, params.config, params.repoRoot);
|
|
18465
18862
|
const existing = await checkpointIdsForRepo(params.stateDir, params.repoRoot);
|
|
@@ -18467,16 +18864,16 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18467
18864
|
throw new Error(RECOVERY_CHECKPOINT_QUOTA);
|
|
18468
18865
|
}
|
|
18469
18866
|
const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
|
|
18470
|
-
const temporary =
|
|
18867
|
+
const temporary = path55.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
|
|
18471
18868
|
const finalDir = checkpointDir(params.stateDir, checkpointId);
|
|
18472
|
-
await
|
|
18473
|
-
await atomicWriteJson(
|
|
18869
|
+
await mkdir12(temporary, { recursive: true, mode: 448 });
|
|
18870
|
+
await atomicWriteJson(path55.join(temporary, "owner.json"), {
|
|
18474
18871
|
version: 1,
|
|
18475
18872
|
pid: process.pid,
|
|
18476
18873
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18477
18874
|
repoRoot: canonicalPath(params.repoRoot)
|
|
18478
18875
|
});
|
|
18479
|
-
await
|
|
18876
|
+
await mkdir12(path55.join(temporary, "blobs"), { recursive: true, mode: 448 });
|
|
18480
18877
|
try {
|
|
18481
18878
|
const entries = [];
|
|
18482
18879
|
const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
|
|
@@ -18485,8 +18882,8 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18485
18882
|
)) {
|
|
18486
18883
|
const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
|
|
18487
18884
|
if (protectedRoots.some((root) => {
|
|
18488
|
-
const relative =
|
|
18489
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
18885
|
+
const relative = path55.relative(root, target);
|
|
18886
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path55.sep}`) && !path55.isAbsolute(relative);
|
|
18490
18887
|
})) {
|
|
18491
18888
|
throw new Error("recovery_protected_path");
|
|
18492
18889
|
}
|
|
@@ -18498,7 +18895,7 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18498
18895
|
entries.push({
|
|
18499
18896
|
path: change.relativePath,
|
|
18500
18897
|
before: await captureRecoverySnapshot(baseline, {
|
|
18501
|
-
blobDir:
|
|
18898
|
+
blobDir: path55.join(temporary, "blobs")
|
|
18502
18899
|
}),
|
|
18503
18900
|
after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
|
|
18504
18901
|
});
|
|
@@ -18535,12 +18932,12 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18535
18932
|
entries
|
|
18536
18933
|
};
|
|
18537
18934
|
const manifestHash = hashValue(canonicalStringify(manifest));
|
|
18538
|
-
await atomicWriteJson(
|
|
18935
|
+
await atomicWriteJson(path55.join(temporary, "manifest.json"), manifest);
|
|
18539
18936
|
await writeRecoveryState(temporary, "prepared", manifestHash);
|
|
18540
18937
|
await fsyncPath2(temporary);
|
|
18541
18938
|
const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
|
|
18542
18939
|
if (projectedBytes > params.config.maxBytes) throw new Error(RECOVERY_CHECKPOINT_QUOTA);
|
|
18543
|
-
await
|
|
18940
|
+
await rename4(temporary, finalDir);
|
|
18544
18941
|
await fsyncPath2(checkpointsRoot(params.stateDir));
|
|
18545
18942
|
return {
|
|
18546
18943
|
checkpointId,
|
|
@@ -18578,7 +18975,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18578
18975
|
} catch {
|
|
18579
18976
|
try {
|
|
18580
18977
|
const raw = JSON.parse(
|
|
18581
|
-
await
|
|
18978
|
+
await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
|
|
18582
18979
|
);
|
|
18583
18980
|
rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
|
|
18584
18981
|
} catch {
|
|
@@ -18612,7 +19009,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18612
19009
|
} catch {
|
|
18613
19010
|
try {
|
|
18614
19011
|
const manifest = JSON.parse(
|
|
18615
|
-
await
|
|
19012
|
+
await readFile13(path55.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
|
|
18616
19013
|
);
|
|
18617
19014
|
if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
|
|
18618
19015
|
if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
|
|
@@ -18637,12 +19034,12 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18637
19034
|
async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
|
|
18638
19035
|
const root = checkpointsRoot(stateDir);
|
|
18639
19036
|
if (!repoRoot) return directorySize(root);
|
|
18640
|
-
if (!
|
|
19037
|
+
if (!existsSync16(root)) return 0;
|
|
18641
19038
|
const expected = canonicalPath(repoRoot);
|
|
18642
19039
|
let total = 0;
|
|
18643
19040
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
18644
19041
|
if (!entry.isDirectory()) continue;
|
|
18645
|
-
const entryPath =
|
|
19042
|
+
const entryPath = path55.join(root, entry.name);
|
|
18646
19043
|
if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
|
|
18647
19044
|
if (await artifactRepoRoot(stateDir, entry.name) === expected) {
|
|
18648
19045
|
total += await directorySize(entryPath);
|
|
@@ -18651,7 +19048,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
|
|
|
18651
19048
|
}
|
|
18652
19049
|
if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
|
|
18653
19050
|
try {
|
|
18654
|
-
const owner = JSON.parse(await
|
|
19051
|
+
const owner = JSON.parse(await readFile13(path55.join(entryPath, "owner.json"), "utf8"));
|
|
18655
19052
|
if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
|
|
18656
19053
|
total += await directorySize(entryPath);
|
|
18657
19054
|
}
|
|
@@ -18693,16 +19090,16 @@ function recoveryFailClosedResult(predicted, reason, signals = []) {
|
|
|
18693
19090
|
init_scrub();
|
|
18694
19091
|
|
|
18695
19092
|
// src/core/transactional/file-checkpoint-backend.ts
|
|
18696
|
-
import { cp, lstat as lstat11, mkdir as
|
|
19093
|
+
import { cp, lstat as lstat11, mkdir as mkdir14, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
|
|
18697
19094
|
import os5 from "node:os";
|
|
18698
|
-
import
|
|
19095
|
+
import path59 from "node:path";
|
|
18699
19096
|
|
|
18700
19097
|
// src/core/transactional/file-checkpoint-git.ts
|
|
18701
19098
|
init_path_utils();
|
|
18702
19099
|
import { spawn as spawn8 } from "node:child_process";
|
|
18703
19100
|
import { createHash as createHash13 } from "node:crypto";
|
|
18704
|
-
import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as
|
|
18705
|
-
import
|
|
19101
|
+
import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile14 } from "node:fs/promises";
|
|
19102
|
+
import path56 from "node:path";
|
|
18706
19103
|
var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
|
|
18707
19104
|
var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
|
|
18708
19105
|
var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
|
|
@@ -18736,7 +19133,7 @@ function rethrowStableFileCheckpointError(error) {
|
|
|
18736
19133
|
}
|
|
18737
19134
|
async function rootGitMetadataPresent(repoRoot) {
|
|
18738
19135
|
try {
|
|
18739
|
-
await lstat9(
|
|
19136
|
+
await lstat9(path56.join(repoRoot, ".git"));
|
|
18740
19137
|
return true;
|
|
18741
19138
|
} catch {
|
|
18742
19139
|
return false;
|
|
@@ -18785,10 +19182,10 @@ function execGit2(repoRoot, args) {
|
|
|
18785
19182
|
}
|
|
18786
19183
|
async function resolveGitPath(repoRoot, gitPath) {
|
|
18787
19184
|
const trimmed = gitPath.trim();
|
|
18788
|
-
if (
|
|
19185
|
+
if (path56.isAbsolute(trimmed)) {
|
|
18789
19186
|
return trimmed;
|
|
18790
19187
|
}
|
|
18791
|
-
return
|
|
19188
|
+
return path56.join(repoRoot, trimmed);
|
|
18792
19189
|
}
|
|
18793
19190
|
async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
|
|
18794
19191
|
await execGit2(sourceRoot, [
|
|
@@ -18830,7 +19227,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
|
|
|
18830
19227
|
destinationRoot,
|
|
18831
19228
|
await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
|
|
18832
19229
|
);
|
|
18833
|
-
const destinationShared =
|
|
19230
|
+
const destinationShared = path56.join(destinationGitDir, path56.basename(sourceShared));
|
|
18834
19231
|
try {
|
|
18835
19232
|
await copyFile3(sourceShared, destinationShared);
|
|
18836
19233
|
} catch (error) {
|
|
@@ -18839,14 +19236,14 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
|
|
|
18839
19236
|
}
|
|
18840
19237
|
async function readGitFile(gitDir, relativePath) {
|
|
18841
19238
|
try {
|
|
18842
|
-
return await
|
|
19239
|
+
return await readFile14(path56.join(gitDir, relativePath));
|
|
18843
19240
|
} catch {
|
|
18844
19241
|
return null;
|
|
18845
19242
|
}
|
|
18846
19243
|
}
|
|
18847
19244
|
async function readAbsoluteGitFile(absolutePath) {
|
|
18848
19245
|
try {
|
|
18849
|
-
return await
|
|
19246
|
+
return await readFile14(absolutePath);
|
|
18850
19247
|
} catch {
|
|
18851
19248
|
return null;
|
|
18852
19249
|
}
|
|
@@ -18863,8 +19260,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
|
|
|
18863
19260
|
repoRoot,
|
|
18864
19261
|
await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
|
|
18865
19262
|
);
|
|
18866
|
-
const relative =
|
|
18867
|
-
const content = typeof relative === "string" && !relative.startsWith("..") && !
|
|
19263
|
+
const relative = path56.resolve(resolved).startsWith(path56.resolve(gitDir)) ? path56.relative(gitDir, resolved) : resolved;
|
|
19264
|
+
const content = typeof relative === "string" && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
|
|
18868
19265
|
if (content !== null) {
|
|
18869
19266
|
hashGitFileContent(hash, gitPath, content);
|
|
18870
19267
|
}
|
|
@@ -18874,13 +19271,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
|
|
|
18874
19271
|
async function hashGitTree(gitDir, relativeDir, hash) {
|
|
18875
19272
|
let names;
|
|
18876
19273
|
try {
|
|
18877
|
-
names = await readdir4(
|
|
19274
|
+
names = await readdir4(path56.join(gitDir, relativeDir));
|
|
18878
19275
|
} catch {
|
|
18879
19276
|
return;
|
|
18880
19277
|
}
|
|
18881
19278
|
for (const name of names.sort()) {
|
|
18882
|
-
const relativePath = relativeDir ?
|
|
18883
|
-
const absolutePath =
|
|
19279
|
+
const relativePath = relativeDir ? path56.join(relativeDir, name) : name;
|
|
19280
|
+
const absolutePath = path56.join(gitDir, relativePath);
|
|
18884
19281
|
let childNames = null;
|
|
18885
19282
|
try {
|
|
18886
19283
|
childNames = await readdir4(absolutePath);
|
|
@@ -18902,7 +19299,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
|
|
|
18902
19299
|
}
|
|
18903
19300
|
async function computeGitMetadataFingerprint(repoRoot) {
|
|
18904
19301
|
const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
|
|
18905
|
-
const gitDir =
|
|
19302
|
+
const gitDir = path56.isAbsolute(gitDirRel) ? gitDirRel : path56.join(repoRoot, gitDirRel);
|
|
18906
19303
|
const hash = createHash13("sha256");
|
|
18907
19304
|
for (const file of [
|
|
18908
19305
|
"HEAD",
|
|
@@ -18925,8 +19322,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18925
19322
|
const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
|
|
18926
19323
|
if (sharedIndex) {
|
|
18927
19324
|
const resolved = await resolveGitPath(repoRoot, sharedIndex);
|
|
18928
|
-
const relative =
|
|
18929
|
-
const content = relative && !relative.startsWith("..") && !
|
|
19325
|
+
const relative = path56.relative(gitDir, resolved);
|
|
19326
|
+
const content = relative && !relative.startsWith("..") && !path56.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
|
|
18930
19327
|
if (content !== null) {
|
|
18931
19328
|
hashGitFileContent(hash, "shared-index", content);
|
|
18932
19329
|
}
|
|
@@ -18937,7 +19334,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18937
19334
|
await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
|
|
18938
19335
|
}
|
|
18939
19336
|
try {
|
|
18940
|
-
const rootGitPath =
|
|
19337
|
+
const rootGitPath = path56.join(repoRoot, ".git");
|
|
18941
19338
|
const rootGitInfo = await lstat9(rootGitPath);
|
|
18942
19339
|
if (rootGitInfo.isFile()) {
|
|
18943
19340
|
const content = await readAbsoluteGitFile(rootGitPath);
|
|
@@ -18953,14 +19350,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18953
19350
|
function resolveExecutionCwdRelative(resourceRoot, cwd) {
|
|
18954
19351
|
const resolvedCwd = canonicalPath(cwd);
|
|
18955
19352
|
const resourceCanonical = canonicalPath(resourceRoot);
|
|
18956
|
-
const relative =
|
|
19353
|
+
const relative = path56.relative(resourceCanonical, resolvedCwd);
|
|
18957
19354
|
if (relative === "" || relative === ".") {
|
|
18958
19355
|
return "";
|
|
18959
19356
|
}
|
|
18960
|
-
if (relative.startsWith("..") ||
|
|
19357
|
+
if (relative.startsWith("..") || path56.isAbsolute(relative)) {
|
|
18961
19358
|
throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
|
|
18962
19359
|
}
|
|
18963
|
-
return relative.split(
|
|
19360
|
+
return relative.split(path56.sep).join("/");
|
|
18964
19361
|
}
|
|
18965
19362
|
|
|
18966
19363
|
// src/core/transactional/file-checkpoint-isolation.ts
|
|
@@ -18981,8 +19378,8 @@ function fileCheckpointIsolationReason(context) {
|
|
|
18981
19378
|
}
|
|
18982
19379
|
|
|
18983
19380
|
// src/core/transactional/file-checkpoint-staging.ts
|
|
18984
|
-
import { readdir as readdir5, readFile as
|
|
18985
|
-
import
|
|
19381
|
+
import { readdir as readdir5, readFile as readFile15, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
|
|
19382
|
+
import path57 from "node:path";
|
|
18986
19383
|
function isOwnerProcessAlive(pid) {
|
|
18987
19384
|
try {
|
|
18988
19385
|
process.kill(pid, 0);
|
|
@@ -18992,12 +19389,12 @@ function isOwnerProcessAlive(pid) {
|
|
|
18992
19389
|
}
|
|
18993
19390
|
}
|
|
18994
19391
|
async function writeOwnerMarker(stagingRoot, marker) {
|
|
18995
|
-
await writeFile8(
|
|
19392
|
+
await writeFile8(path57.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
|
|
18996
19393
|
`, "utf8");
|
|
18997
19394
|
}
|
|
18998
19395
|
async function readOwnerMarker(stagingRoot) {
|
|
18999
19396
|
try {
|
|
19000
|
-
const raw = await
|
|
19397
|
+
const raw = await readFile15(path57.join(stagingRoot, "owner.json"), "utf8");
|
|
19001
19398
|
return JSON.parse(raw.trim());
|
|
19002
19399
|
} catch {
|
|
19003
19400
|
return null;
|
|
@@ -19015,7 +19412,7 @@ async function collectDeadOwnerStaging(parentDir) {
|
|
|
19015
19412
|
if (!name.startsWith("belay-file-checkpoint-")) {
|
|
19016
19413
|
continue;
|
|
19017
19414
|
}
|
|
19018
|
-
const stagingRoot =
|
|
19415
|
+
const stagingRoot = path57.join(parentDir, name);
|
|
19019
19416
|
const marker = await readOwnerMarker(stagingRoot);
|
|
19020
19417
|
if (!marker) {
|
|
19021
19418
|
dead.push(stagingRoot);
|
|
@@ -19038,7 +19435,7 @@ import { constants as fsConstants2 } from "node:fs";
|
|
|
19038
19435
|
import {
|
|
19039
19436
|
copyFile as copyFile4,
|
|
19040
19437
|
lstat as lstat10,
|
|
19041
|
-
mkdir as
|
|
19438
|
+
mkdir as mkdir13,
|
|
19042
19439
|
mkdtemp as mkdtemp4,
|
|
19043
19440
|
readlink as readlink5,
|
|
19044
19441
|
rm as rm8,
|
|
@@ -19047,17 +19444,17 @@ import {
|
|
|
19047
19444
|
writeFile as writeFile9
|
|
19048
19445
|
} from "node:fs/promises";
|
|
19049
19446
|
import os4 from "node:os";
|
|
19050
|
-
import
|
|
19447
|
+
import path58 from "node:path";
|
|
19051
19448
|
var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
|
|
19052
19449
|
async function chmodSafe2(target, mode) {
|
|
19053
19450
|
try {
|
|
19054
|
-
const { chmod:
|
|
19055
|
-
await
|
|
19451
|
+
const { chmod: chmod5 } = await import("node:fs/promises");
|
|
19452
|
+
await chmod5(target, mode & 511);
|
|
19056
19453
|
} catch {
|
|
19057
19454
|
}
|
|
19058
19455
|
}
|
|
19059
19456
|
async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
|
|
19060
|
-
await
|
|
19457
|
+
await mkdir13(path58.dirname(destinationPath), { recursive: true });
|
|
19061
19458
|
if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
|
|
19062
19459
|
try {
|
|
19063
19460
|
await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
|
|
@@ -19079,11 +19476,11 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
|
|
|
19079
19476
|
const info = await lstat10(sourcePath);
|
|
19080
19477
|
await rm8(destinationPath, { force: true, recursive: false });
|
|
19081
19478
|
if (info.isDirectory() && !info.isSymbolicLink()) {
|
|
19082
|
-
await
|
|
19479
|
+
await mkdir13(destinationPath, { recursive: true, mode: info.mode & 511 });
|
|
19083
19480
|
return strategy;
|
|
19084
19481
|
}
|
|
19085
19482
|
if (info.isSymbolicLink()) {
|
|
19086
|
-
await
|
|
19483
|
+
await mkdir13(path58.dirname(destinationPath), { recursive: true });
|
|
19087
19484
|
await symlink4(await readlink5(sourcePath), destinationPath);
|
|
19088
19485
|
return strategy;
|
|
19089
19486
|
}
|
|
@@ -19139,9 +19536,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
|
|
|
19139
19536
|
async function probeFileCloneStrategy() {
|
|
19140
19537
|
let tempDir = null;
|
|
19141
19538
|
try {
|
|
19142
|
-
tempDir = await mkdtemp4(
|
|
19143
|
-
const source =
|
|
19144
|
-
const destination =
|
|
19539
|
+
tempDir = await mkdtemp4(path58.join(os4.tmpdir(), "belay-clone-probe-"));
|
|
19540
|
+
const source = path58.join(tempDir, "source.txt");
|
|
19541
|
+
const destination = path58.join(tempDir, "dest.txt");
|
|
19145
19542
|
await writeFile9(source, "probe\n");
|
|
19146
19543
|
if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
|
|
19147
19544
|
try {
|
|
@@ -19294,11 +19691,11 @@ async function protectedRootState(root) {
|
|
|
19294
19691
|
return `directory:${node.hash}:${index.treeHash}`;
|
|
19295
19692
|
}
|
|
19296
19693
|
function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
|
|
19297
|
-
const relative =
|
|
19298
|
-
if (relative === "" || relative.startsWith("..") ||
|
|
19694
|
+
const relative = path59.relative(path59.resolve(resourceRoot), path59.resolve(protectedRoot));
|
|
19695
|
+
if (relative === "" || relative.startsWith("..") || path59.isAbsolute(relative)) {
|
|
19299
19696
|
return null;
|
|
19300
19697
|
}
|
|
19301
|
-
return
|
|
19698
|
+
return path59.join(executionRoot, relative);
|
|
19302
19699
|
}
|
|
19303
19700
|
async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
|
|
19304
19701
|
const states = /* @__PURE__ */ new Map();
|
|
@@ -19323,15 +19720,15 @@ async function directoryByteSize(root, deadlineMs) {
|
|
|
19323
19720
|
}
|
|
19324
19721
|
let total = 0;
|
|
19325
19722
|
for (const name of await readdir6(root)) {
|
|
19326
|
-
total += await directoryByteSize(
|
|
19723
|
+
total += await directoryByteSize(path59.join(root, name), deadlineMs);
|
|
19327
19724
|
}
|
|
19328
19725
|
return total;
|
|
19329
19726
|
}
|
|
19330
19727
|
async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
|
|
19331
19728
|
const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
|
|
19332
|
-
const sourceGitDir =
|
|
19333
|
-
const relativeGitDir =
|
|
19334
|
-
const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ?
|
|
19729
|
+
const sourceGitDir = path59.isAbsolute(gitDirRel) ? gitDirRel : path59.join(sourceRoot, gitDirRel);
|
|
19730
|
+
const relativeGitDir = path59.relative(path59.resolve(sourceRoot), path59.resolve(sourceGitDir));
|
|
19731
|
+
const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path59.join(destinationRoot, relativeGitDir) : path59.join(destinationRoot, ".git");
|
|
19335
19732
|
await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
|
|
19336
19733
|
}
|
|
19337
19734
|
async function prepareDirtyGitSnapshot(context) {
|
|
@@ -19340,7 +19737,7 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19340
19737
|
const quotas = context.fileCheckpoint;
|
|
19341
19738
|
const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
|
|
19342
19739
|
await removeDeadOwnerStaging(os5.tmpdir());
|
|
19343
|
-
const stagingRoot = await mkdtemp5(
|
|
19740
|
+
const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
|
|
19344
19741
|
await writeOwnerMarker(stagingRoot, {
|
|
19345
19742
|
version: 1,
|
|
19346
19743
|
pid: process.pid,
|
|
@@ -19348,8 +19745,8 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19348
19745
|
resourceRoot: context.repoRoot,
|
|
19349
19746
|
backend: "file_checkpoint"
|
|
19350
19747
|
});
|
|
19351
|
-
const baselineRoot =
|
|
19352
|
-
const executionRoot =
|
|
19748
|
+
const baselineRoot = path59.join(stagingRoot, "baseline");
|
|
19749
|
+
const executionRoot = path59.join(stagingRoot, "execution");
|
|
19353
19750
|
try {
|
|
19354
19751
|
resolveExecutionCwdRelative(context.repoRoot, context.cwd);
|
|
19355
19752
|
const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
|
|
@@ -19383,7 +19780,7 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19383
19780
|
throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
|
|
19384
19781
|
}
|
|
19385
19782
|
await writeFile10(
|
|
19386
|
-
|
|
19783
|
+
path59.join(stagingRoot, "baseline-index.json"),
|
|
19387
19784
|
`${JSON.stringify(baselineIndex)}
|
|
19388
19785
|
`,
|
|
19389
19786
|
"utf8"
|
|
@@ -19433,7 +19830,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19433
19830
|
const quotas = context.fileCheckpoint;
|
|
19434
19831
|
const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
|
|
19435
19832
|
await removeDeadOwnerStaging(os5.tmpdir());
|
|
19436
|
-
const stagingRoot = await mkdtemp5(
|
|
19833
|
+
const stagingRoot = await mkdtemp5(path59.join(os5.tmpdir(), "belay-file-checkpoint-"));
|
|
19437
19834
|
await writeOwnerMarker(stagingRoot, {
|
|
19438
19835
|
version: 1,
|
|
19439
19836
|
pid: process.pid,
|
|
@@ -19441,8 +19838,8 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19441
19838
|
resourceRoot: context.repoRoot,
|
|
19442
19839
|
backend: "file_checkpoint"
|
|
19443
19840
|
});
|
|
19444
|
-
const baselineRoot =
|
|
19445
|
-
const executionRoot =
|
|
19841
|
+
const baselineRoot = path59.join(stagingRoot, "baseline");
|
|
19842
|
+
const executionRoot = path59.join(stagingRoot, "execution");
|
|
19446
19843
|
try {
|
|
19447
19844
|
resolveExecutionCwdRelative(context.repoRoot, context.cwd);
|
|
19448
19845
|
const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
|
|
@@ -19451,7 +19848,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19451
19848
|
quotas,
|
|
19452
19849
|
deadlineMs
|
|
19453
19850
|
});
|
|
19454
|
-
await
|
|
19851
|
+
await mkdir14(baselineRoot, { recursive: true });
|
|
19455
19852
|
const baselineIndex = await buildFileTreeIndex({
|
|
19456
19853
|
resourceRoot: baselineRoot,
|
|
19457
19854
|
excludedRoots,
|
|
@@ -19471,7 +19868,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19471
19868
|
throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
|
|
19472
19869
|
}
|
|
19473
19870
|
await writeFile10(
|
|
19474
|
-
|
|
19871
|
+
path59.join(stagingRoot, "baseline-index.json"),
|
|
19475
19872
|
`${JSON.stringify(baselineIndex)}
|
|
19476
19873
|
`,
|
|
19477
19874
|
"utf8"
|
|
@@ -19481,7 +19878,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19481
19878
|
quotas,
|
|
19482
19879
|
deadlineMs
|
|
19483
19880
|
});
|
|
19484
|
-
await
|
|
19881
|
+
await mkdir14(executionRoot, { recursive: true });
|
|
19485
19882
|
const finalSourceIndex = await buildFileTreeIndex({
|
|
19486
19883
|
resourceRoot: context.repoRoot,
|
|
19487
19884
|
excludedRoots,
|
|
@@ -19824,10 +20221,10 @@ async function selectTransactionalBackend(context) {
|
|
|
19824
20221
|
}
|
|
19825
20222
|
|
|
19826
20223
|
// src/core/transactional/diff-evaluator.ts
|
|
19827
|
-
import
|
|
20224
|
+
import path60 from "node:path";
|
|
19828
20225
|
init_path_utils();
|
|
19829
20226
|
function categorizeChange(change, ctx) {
|
|
19830
|
-
const absolutePath = canonicalPath(
|
|
20227
|
+
const absolutePath = canonicalPath(path60.join(ctx.repoRoot, change.relativePath));
|
|
19831
20228
|
if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
|
|
19832
20229
|
return "repo_outside";
|
|
19833
20230
|
}
|
|
@@ -20377,17 +20774,55 @@ function formatJudgeInfrastructureDenyMessage(params) {
|
|
|
20377
20774
|
}
|
|
20378
20775
|
|
|
20379
20776
|
// src/core/notify.ts
|
|
20777
|
+
init_path_utils();
|
|
20380
20778
|
import { execFile } from "node:child_process";
|
|
20779
|
+
import path61 from "node:path";
|
|
20381
20780
|
import { promisify } from "node:util";
|
|
20382
20781
|
var execFileAsync = promisify(execFile);
|
|
20383
|
-
|
|
20384
|
-
|
|
20385
|
-
|
|
20782
|
+
var LOOPBACK_WEBHOOK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
20783
|
+
function webhookConfigIssue(url) {
|
|
20784
|
+
let parsed;
|
|
20785
|
+
try {
|
|
20786
|
+
parsed = new URL(url);
|
|
20787
|
+
} catch {
|
|
20788
|
+
return `notifications.webhookUrl is invalid: ${url}`;
|
|
20789
|
+
}
|
|
20790
|
+
if (parsed.protocol === "https:") {
|
|
20791
|
+
return null;
|
|
20792
|
+
}
|
|
20793
|
+
const normalizedHostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
20794
|
+
if (parsed.protocol === "http:" && LOOPBACK_WEBHOOK_HOSTS.has(normalizedHostname)) {
|
|
20795
|
+
return null;
|
|
20796
|
+
}
|
|
20797
|
+
return `notifications.webhookUrl must use https (http is allowed only for localhost, 127.0.0.1, or ::1): ${url}`;
|
|
20798
|
+
}
|
|
20799
|
+
function commandHookConfigIssue(commandHook, repoRoot) {
|
|
20800
|
+
if (!path61.isAbsolute(commandHook)) {
|
|
20801
|
+
return `notifications.commandHook must be an absolute path: ${commandHook}`;
|
|
20802
|
+
}
|
|
20803
|
+
if (pathWithinRoot(canonicalPath(repoRoot), canonicalPath(commandHook))) {
|
|
20804
|
+
return `notifications.commandHook must not be inside the repository: ${commandHook}`;
|
|
20805
|
+
}
|
|
20806
|
+
return null;
|
|
20807
|
+
}
|
|
20808
|
+
async function notifyDeny(config, event, deps = {
|
|
20809
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
20810
|
+
execFile: (file, args, options) => execFileAsync(file, [...args], options)
|
|
20811
|
+
}) {
|
|
20812
|
+
const payload = JSON.stringify({
|
|
20813
|
+
approvalId: event.approvalId,
|
|
20814
|
+
reason: event.reason,
|
|
20815
|
+
summary: event.summary,
|
|
20816
|
+
repoRoot: event.repoRoot,
|
|
20817
|
+
fingerprint: event.fingerprint
|
|
20818
|
+
});
|
|
20819
|
+
const webhookIssue = config.webhookUrl ? webhookConfigIssue(config.webhookUrl) : null;
|
|
20820
|
+
if (config.webhookUrl && !webhookIssue) {
|
|
20386
20821
|
try {
|
|
20387
20822
|
const controller = new AbortController();
|
|
20388
20823
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
20389
20824
|
try {
|
|
20390
|
-
await fetch(config.webhookUrl, {
|
|
20825
|
+
await deps.fetch(config.webhookUrl, {
|
|
20391
20826
|
method: "POST",
|
|
20392
20827
|
headers: { "content-type": "application/json" },
|
|
20393
20828
|
body: payload,
|
|
@@ -20399,17 +20834,16 @@ async function notifyDeny(config, event) {
|
|
|
20399
20834
|
} catch {
|
|
20400
20835
|
}
|
|
20401
20836
|
}
|
|
20402
|
-
|
|
20837
|
+
const commandHookIssue = config.commandHook ? commandHookConfigIssue(config.commandHook, event.repoRoot) : null;
|
|
20838
|
+
if (config.commandHook && !commandHookIssue) {
|
|
20403
20839
|
try {
|
|
20404
|
-
await
|
|
20840
|
+
await deps.execFile(config.commandHook, [], {
|
|
20405
20841
|
env: {
|
|
20406
|
-
...process.env,
|
|
20407
20842
|
BELAY_APPROVAL_ID: event.approvalId,
|
|
20408
20843
|
BELAY_REASON: event.reason,
|
|
20409
20844
|
BELAY_SUMMARY: event.summary,
|
|
20410
20845
|
BELAY_REPO_ROOT: event.repoRoot,
|
|
20411
|
-
BELAY_FINGERPRINT: event.fingerprint
|
|
20412
|
-
BELAY_APPROVAL_TOKEN: event.approvalToken ?? ""
|
|
20846
|
+
BELAY_FINGERPRINT: event.fingerprint
|
|
20413
20847
|
}
|
|
20414
20848
|
});
|
|
20415
20849
|
} catch {
|
|
@@ -20419,9 +20853,10 @@ async function notifyDeny(config, event) {
|
|
|
20419
20853
|
|
|
20420
20854
|
// src/adapters/shared/gate-runtime.ts
|
|
20421
20855
|
init_path_utils();
|
|
20856
|
+
init_repo_config_trust();
|
|
20422
20857
|
|
|
20423
20858
|
// src/adapters/layouts/protected-paths.ts
|
|
20424
|
-
import
|
|
20859
|
+
import path62 from "node:path";
|
|
20425
20860
|
function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
|
|
20426
20861
|
const roots = [
|
|
20427
20862
|
layout.configPath(repoRoot),
|
|
@@ -20433,7 +20868,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
|
|
|
20433
20868
|
if (controlPlaneDir) {
|
|
20434
20869
|
roots.push(controlPlaneDir);
|
|
20435
20870
|
}
|
|
20436
|
-
return roots.map((entry) =>
|
|
20871
|
+
return roots.map((entry) => path62.resolve(entry));
|
|
20437
20872
|
}
|
|
20438
20873
|
|
|
20439
20874
|
// src/adapters/shared/gate-runtime.ts
|
|
@@ -20474,7 +20909,7 @@ async function appendReplayAuditSafely(ctx, deps, event) {
|
|
|
20474
20909
|
}
|
|
20475
20910
|
async function loadJsonFile(filePath, fallback) {
|
|
20476
20911
|
try {
|
|
20477
|
-
const raw = await
|
|
20912
|
+
const raw = await readFile16(filePath, "utf8");
|
|
20478
20913
|
return JSON.parse(raw);
|
|
20479
20914
|
} catch {
|
|
20480
20915
|
return fallback;
|
|
@@ -20483,11 +20918,18 @@ async function loadJsonFile(filePath, fallback) {
|
|
|
20483
20918
|
function createDefaultGateRuntimeDeps() {
|
|
20484
20919
|
return {
|
|
20485
20920
|
async readConfig(configPath) {
|
|
20486
|
-
|
|
20921
|
+
try {
|
|
20922
|
+
return JSON.parse(await readFile16(configPath, "utf8"));
|
|
20923
|
+
} catch (error) {
|
|
20924
|
+
if (error.code === "ENOENT") {
|
|
20925
|
+
return {};
|
|
20926
|
+
}
|
|
20927
|
+
throw error;
|
|
20928
|
+
}
|
|
20487
20929
|
},
|
|
20488
20930
|
async appendAudit(ctx, event) {
|
|
20489
|
-
const auditPath =
|
|
20490
|
-
await
|
|
20931
|
+
const auditPath = path63.join(ctx.repoRoot, ctx.config.audit.logPath);
|
|
20932
|
+
await mkdir15(path63.dirname(auditPath), { recursive: true });
|
|
20491
20933
|
const provenance = auditProvenance(ctx.config);
|
|
20492
20934
|
const record = {
|
|
20493
20935
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -20520,7 +20962,7 @@ function createDefaultGateRuntimeDeps() {
|
|
|
20520
20962
|
};
|
|
20521
20963
|
},
|
|
20522
20964
|
async writeApprovals(filePath, state) {
|
|
20523
|
-
await
|
|
20965
|
+
await mkdir15(path63.dirname(filePath), { recursive: true });
|
|
20524
20966
|
await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
|
|
20525
20967
|
`, "utf8");
|
|
20526
20968
|
},
|
|
@@ -20541,10 +20983,11 @@ function createDefaultGateRuntimeDeps() {
|
|
|
20541
20983
|
}
|
|
20542
20984
|
async function resolveGateConfig(ctx, deps) {
|
|
20543
20985
|
const loaded = await deps.readConfig(ctx.configPath);
|
|
20986
|
+
await assertRepoConfigTrusted(ctx.repoRoot, ctx.layout.name, loaded);
|
|
20544
20987
|
let teamConfig = null;
|
|
20545
20988
|
const teamPath = teamConfigPath();
|
|
20546
|
-
if (
|
|
20547
|
-
teamConfig = JSON.parse(await
|
|
20989
|
+
if (existsSync17(teamPath)) {
|
|
20990
|
+
teamConfig = JSON.parse(await readFile16(teamPath, "utf8"));
|
|
20548
20991
|
}
|
|
20549
20992
|
return resolveLayeredConfig({
|
|
20550
20993
|
repoConfig: loaded,
|
|
@@ -20682,7 +21125,7 @@ function deriveWorkspaceRootScopeHint(params) {
|
|
|
20682
21125
|
if (!targetPath) {
|
|
20683
21126
|
return void 0;
|
|
20684
21127
|
}
|
|
20685
|
-
const candidateRoot = canonicalPath(
|
|
21128
|
+
const candidateRoot = canonicalPath(path63.dirname(targetPath));
|
|
20686
21129
|
const validation = validateTrustedWorkspaceRootCandidate({
|
|
20687
21130
|
candidatePath: candidateRoot,
|
|
20688
21131
|
repoRoot: action.repoRoot,
|
|
@@ -21202,40 +21645,6 @@ async function evaluateGatedAction(ctx, deps, params) {
|
|
|
21202
21645
|
scopeHintPayload: params.payload
|
|
21203
21646
|
});
|
|
21204
21647
|
}
|
|
21205
|
-
async function gateUnmappedToolVerdict(ctx, deps, toolName, payload) {
|
|
21206
|
-
const scrubOpts = scrubOptionsFromConfig(ctx.config);
|
|
21207
|
-
const replayPayload = fingerprintReplayPayload("tool", payload, scrubOpts) ?? {};
|
|
21208
|
-
const result = {
|
|
21209
|
-
verdict: "deny_pending_approval",
|
|
21210
|
-
reason: "unmapped_tool",
|
|
21211
|
-
summary: toolName,
|
|
21212
|
-
fingerprint: toolFingerprint(toolName, replayPayload, ctx.repoRoot),
|
|
21213
|
-
assessment: {
|
|
21214
|
-
reversibility: "irreversible",
|
|
21215
|
-
external: false,
|
|
21216
|
-
blastRadius: "unknown Codex tool action",
|
|
21217
|
-
confidence: 0.5,
|
|
21218
|
-
signals: ["unmapped_tool"]
|
|
21219
|
-
}
|
|
21220
|
-
};
|
|
21221
|
-
return gateDecisionToVerdict(ctx, deps, "tool", result, {
|
|
21222
|
-
approvalInput: {
|
|
21223
|
-
input: toolName,
|
|
21224
|
-
inputKind: "tool",
|
|
21225
|
-
cwd: ctx.repoRoot,
|
|
21226
|
-
toolName,
|
|
21227
|
-
payload: replayPayload
|
|
21228
|
-
},
|
|
21229
|
-
replayAction: {
|
|
21230
|
-
kind: "tool",
|
|
21231
|
-
cwd: ctx.repoRoot,
|
|
21232
|
-
toolName,
|
|
21233
|
-
payload: replayPayload,
|
|
21234
|
-
fingerprint: result.fingerprint,
|
|
21235
|
-
repoRoot: ctx.repoRoot
|
|
21236
|
-
}
|
|
21237
|
-
});
|
|
21238
|
-
}
|
|
21239
21648
|
async function consumeCapabilityGrantIfUsed(ctx, deps, result) {
|
|
21240
21649
|
if (result.reason !== "capability_grant" && result.authorizationDecision?.matchedRule !== "grant.exact") {
|
|
21241
21650
|
return true;
|
|
@@ -21292,7 +21701,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21292
21701
|
event: auditEvent,
|
|
21293
21702
|
sourceEvent,
|
|
21294
21703
|
kind,
|
|
21295
|
-
repoLabel:
|
|
21704
|
+
repoLabel: path63.basename(ctx.repoRoot),
|
|
21296
21705
|
...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
|
|
21297
21706
|
fingerprint: result.fingerprint,
|
|
21298
21707
|
summary: result.normalizedCommand ?? result.summary ?? "",
|
|
@@ -21324,21 +21733,6 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21324
21733
|
if (created) {
|
|
21325
21734
|
await recordGateApprovalAsk(stateDir, result.reason, false);
|
|
21326
21735
|
}
|
|
21327
|
-
let approvalToken;
|
|
21328
|
-
try {
|
|
21329
|
-
approvalToken = await issueApprovalToken(
|
|
21330
|
-
{
|
|
21331
|
-
approvalId: approval.approvalId,
|
|
21332
|
-
fingerprint: approval.fingerprint,
|
|
21333
|
-
repoRoot: approval.repoRoot,
|
|
21334
|
-
issuedAt: approval.createdAt,
|
|
21335
|
-
expiresAt: approval.expiresAt
|
|
21336
|
-
},
|
|
21337
|
-
configuredControlPlaneDir(ctx.config)
|
|
21338
|
-
);
|
|
21339
|
-
} catch {
|
|
21340
|
-
approvalToken = void 0;
|
|
21341
|
-
}
|
|
21342
21736
|
const denialReason = failure?.reason ?? result.reason;
|
|
21343
21737
|
if (ctx.config.notifications.webhookUrl || ctx.config.notifications.commandHook) {
|
|
21344
21738
|
await notifyDeny(ctx.config.notifications, {
|
|
@@ -21346,8 +21740,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21346
21740
|
reason: denialReason,
|
|
21347
21741
|
summary: result.normalizedCommand ?? result.summary ?? "",
|
|
21348
21742
|
repoRoot: ctx.repoRoot,
|
|
21349
|
-
fingerprint: result.fingerprint
|
|
21350
|
-
approvalToken
|
|
21743
|
+
fingerprint: result.fingerprint
|
|
21351
21744
|
});
|
|
21352
21745
|
}
|
|
21353
21746
|
const failureAuditFields = typeof failure?.auditFields === "function" ? failure.auditFields(approval) : failure?.auditFields;
|
|
@@ -21762,38 +22155,38 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
|
|
|
21762
22155
|
}
|
|
21763
22156
|
|
|
21764
22157
|
// src/adapters/shared/repo-root.ts
|
|
21765
|
-
import { existsSync as
|
|
21766
|
-
import
|
|
22158
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
22159
|
+
import path64 from "node:path";
|
|
21767
22160
|
function belayConfigPath(current, adapterName) {
|
|
21768
22161
|
if (adapterName === "cursor") {
|
|
21769
|
-
return
|
|
22162
|
+
return path64.join(current, ".cursor", "belay.config.json");
|
|
21770
22163
|
}
|
|
21771
22164
|
if (adapterName === "claude") {
|
|
21772
|
-
return
|
|
22165
|
+
return path64.join(current, ".claude", "belay.config.json");
|
|
21773
22166
|
}
|
|
21774
|
-
return
|
|
22167
|
+
return path64.join(current, ".codex", "belay.config.json");
|
|
21775
22168
|
}
|
|
21776
22169
|
function markerMatches(current, marker, layout) {
|
|
21777
|
-
const markerPath =
|
|
21778
|
-
if (!
|
|
22170
|
+
const markerPath = path64.join(current, marker);
|
|
22171
|
+
if (!existsSync18(markerPath)) {
|
|
21779
22172
|
return false;
|
|
21780
22173
|
}
|
|
21781
22174
|
if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
|
|
21782
|
-
return
|
|
22175
|
+
return existsSync18(belayConfigPath(current, layout.name));
|
|
21783
22176
|
}
|
|
21784
22177
|
return true;
|
|
21785
22178
|
}
|
|
21786
22179
|
function findRepoRoot(startPath, layout) {
|
|
21787
|
-
let current =
|
|
22180
|
+
let current = path64.resolve(startPath);
|
|
21788
22181
|
while (true) {
|
|
21789
22182
|
for (const marker of layout.repoRootMarkers) {
|
|
21790
22183
|
if (markerMatches(current, marker, layout)) {
|
|
21791
22184
|
return current;
|
|
21792
22185
|
}
|
|
21793
22186
|
}
|
|
21794
|
-
const parent =
|
|
22187
|
+
const parent = path64.dirname(current);
|
|
21795
22188
|
if (parent === current) {
|
|
21796
|
-
return
|
|
22189
|
+
return path64.resolve(startPath);
|
|
21797
22190
|
}
|
|
21798
22191
|
current = parent;
|
|
21799
22192
|
}
|
|
@@ -21827,7 +22220,7 @@ function resolveCodexActionCwd(payload, fallbackCwd = process2.cwd(), options =
|
|
|
21827
22220
|
const nestedCwd = options.includeToolInputCwd ? nonEmptyPathString(toolInput?.working_directory) ?? nonEmptyPathString(toolInput?.cwd) : void 0;
|
|
21828
22221
|
const payloadCwd = nonEmptyPathString(payload.cwd);
|
|
21829
22222
|
const workspaceRoot = Array.isArray(payload.workspace_roots) ? payload.workspace_roots.map(nonEmptyPathString).find(Boolean) : void 0;
|
|
21830
|
-
return
|
|
22223
|
+
return path65.resolve(nestedCwd ?? payloadCwd ?? workspaceRoot ?? fallbackCwd);
|
|
21831
22224
|
}
|
|
21832
22225
|
async function loadRuntimeContext(cwd) {
|
|
21833
22226
|
const repoRoot = findRepoRoot(cwd, codexLayout);
|
|
@@ -21943,13 +22336,14 @@ async function runToolGateHook(eventName) {
|
|
|
21943
22336
|
const ctx = await loadRuntimeContext(cwd);
|
|
21944
22337
|
const deps = createDefaultGateRuntimeDeps();
|
|
21945
22338
|
if (!kind) {
|
|
21946
|
-
const
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
22339
|
+
const normalizedPayload2 = normalizeCodexToolPayload("tool", payload);
|
|
22340
|
+
const verdict3 = await evaluateGatedAction(ctx, deps, {
|
|
22341
|
+
kind: "tool",
|
|
22342
|
+
cwd,
|
|
22343
|
+
payload: normalizedPayload2,
|
|
22344
|
+
toolName,
|
|
22345
|
+
sourceEvent: eventName
|
|
22346
|
+
});
|
|
21953
22347
|
jsonResponse(gateVerdictToCodexPreToolUseResponse(verdict3));
|
|
21954
22348
|
return;
|
|
21955
22349
|
}
|