@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
|
@@ -1847,7 +1847,7 @@ function normalizeConfig(config) {
|
|
|
1847
1847
|
policy: {
|
|
1848
1848
|
unknownLocalEffect: v4.policy?.unknownLocalEffect === "deny" ? "deny" : v4.policy?.unknownLocalEffect === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unknownLocalEffect,
|
|
1849
1849
|
unparseableShell: v4.policy?.unparseableShell === "deny" ? "deny" : v4.policy?.unparseableShell === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unparseableShell,
|
|
1850
|
-
codexUnmappedTool:
|
|
1850
|
+
codexUnmappedTool: "allow",
|
|
1851
1851
|
fenceWarnThreshold: typeof v4.policy?.fenceWarnThreshold === "number" && v4.policy.fenceWarnThreshold > 0 && v4.policy.fenceWarnThreshold <= 1 ? v4.policy.fenceWarnThreshold : DEFAULT_FENCE_WARN_THRESHOLD,
|
|
1852
1852
|
confidenceThresholds: {
|
|
1853
1853
|
allow: typeof v4.policy?.confidenceThresholds?.allow === "number" ? v4.policy.confidenceThresholds.allow : DEFAULT_CONFIDENCE_THRESHOLDS.allow,
|
|
@@ -2149,7 +2149,7 @@ var init_config = __esm({
|
|
|
2149
2149
|
DEFAULT_POLICY_V3 = {
|
|
2150
2150
|
unknownLocalEffect: "allow_flagged",
|
|
2151
2151
|
unparseableShell: "deny",
|
|
2152
|
-
codexUnmappedTool: "
|
|
2152
|
+
codexUnmappedTool: "allow",
|
|
2153
2153
|
confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
|
|
2154
2154
|
modelAssist: { ...DEFAULT_MODEL_ASSIST },
|
|
2155
2155
|
transactional: { ...DEFAULT_TRANSACTIONAL_V3 },
|
|
@@ -3510,9 +3510,125 @@ 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
|
+
function isRepoConfigTrustError(error) {
|
|
3601
|
+
return error instanceof RepoConfigTrustError;
|
|
3602
|
+
}
|
|
3603
|
+
async function assertRepoConfigTrusted(repoRoot, adapter, rawConfig) {
|
|
3604
|
+
const status = await inspectRepoConfigTrust(repoRoot, adapter, rawConfig);
|
|
3605
|
+
if (status.trusted) {
|
|
3606
|
+
return;
|
|
3607
|
+
}
|
|
3608
|
+
throw new RepoConfigTrustError(status);
|
|
3609
|
+
}
|
|
3610
|
+
var TRUST_MESSAGE, RepoConfigTrustError;
|
|
3611
|
+
var init_repo_config_trust = __esm({
|
|
3612
|
+
"src/core/repo-config-trust.ts"() {
|
|
3613
|
+
"use strict";
|
|
3614
|
+
init_config();
|
|
3615
|
+
init_fingerprint2();
|
|
3616
|
+
init_path_utils();
|
|
3617
|
+
TRUST_MESSAGE = "Repository config is not trusted. Review it, then run `belay config trust`.";
|
|
3618
|
+
RepoConfigTrustError = class extends Error {
|
|
3619
|
+
status;
|
|
3620
|
+
constructor(status) {
|
|
3621
|
+
super(TRUST_MESSAGE);
|
|
3622
|
+
this.name = "RepoConfigTrustError";
|
|
3623
|
+
this.status = status;
|
|
3624
|
+
}
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
});
|
|
3628
|
+
|
|
3629
|
+
// src/config-io.ts
|
|
3630
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
3631
|
+
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
3632
|
function resolveAdapterName(config) {
|
|
3517
3633
|
if (config.adapter === "claude") {
|
|
3518
3634
|
return "claude";
|
|
@@ -3523,10 +3639,10 @@ function resolveAdapterName(config) {
|
|
|
3523
3639
|
return "cursor";
|
|
3524
3640
|
}
|
|
3525
3641
|
function detectAdapterName(repoRoot) {
|
|
3526
|
-
if (
|
|
3642
|
+
if (existsSync3(configPathFor(repoRoot, "claude"))) {
|
|
3527
3643
|
return "claude";
|
|
3528
3644
|
}
|
|
3529
|
-
if (
|
|
3645
|
+
if (existsSync3(configPathFor(repoRoot, "codex"))) {
|
|
3530
3646
|
return "codex";
|
|
3531
3647
|
}
|
|
3532
3648
|
return "cursor";
|
|
@@ -3547,20 +3663,20 @@ async function loadLayeredConfig(repoRoot, adapter = detectAdapterName(repoRoot)
|
|
|
3547
3663
|
const layout = getAdapterLayout(adapter);
|
|
3548
3664
|
const configPath = configPathFor(repoRoot, adapter);
|
|
3549
3665
|
let repoConfig = {};
|
|
3550
|
-
if (
|
|
3551
|
-
repoConfig = JSON.parse(await
|
|
3666
|
+
if (existsSync3(configPath)) {
|
|
3667
|
+
repoConfig = JSON.parse(await readFile3(configPath, "utf8"));
|
|
3552
3668
|
}
|
|
3553
3669
|
let teamConfig = null;
|
|
3554
3670
|
const teamPath = teamConfigPath();
|
|
3555
|
-
if (
|
|
3556
|
-
teamConfig = JSON.parse(await
|
|
3671
|
+
if (existsSync3(teamPath)) {
|
|
3672
|
+
teamConfig = JSON.parse(await readFile3(teamPath, "utf8"));
|
|
3557
3673
|
}
|
|
3558
3674
|
return resolveLayeredConfig({
|
|
3559
3675
|
repoConfig,
|
|
3560
3676
|
adapterDefaults: layout.defaultConfig(repoRoot),
|
|
3561
3677
|
teamConfig,
|
|
3562
3678
|
teamConfigPath: teamPath,
|
|
3563
|
-
repoConfigPath:
|
|
3679
|
+
repoConfigPath: existsSync3(configPath) ? configPath : void 0
|
|
3564
3680
|
});
|
|
3565
3681
|
}
|
|
3566
3682
|
async function loadConfigFile(repoRoot, adapter = detectAdapterName(repoRoot)) {
|
|
@@ -3575,21 +3691,22 @@ var init_config_io = __esm({
|
|
|
3575
3691
|
init_approval_state_mutation();
|
|
3576
3692
|
init_config();
|
|
3577
3693
|
init_config_layers();
|
|
3694
|
+
init_repo_config_trust();
|
|
3578
3695
|
}
|
|
3579
3696
|
});
|
|
3580
3697
|
|
|
3581
3698
|
// src/adapters/cursor/runtime-entry.ts
|
|
3582
3699
|
init_approval();
|
|
3583
|
-
import
|
|
3700
|
+
import path67 from "node:path";
|
|
3584
3701
|
import process2 from "node:process";
|
|
3585
3702
|
|
|
3586
3703
|
// src/core/approval-repo-lookup.ts
|
|
3587
3704
|
init_config_io();
|
|
3588
|
-
import { readFile as
|
|
3589
|
-
import
|
|
3705
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
3706
|
+
import path12 from "node:path";
|
|
3590
3707
|
async function approvalStateContainsId(filePath, approvalId) {
|
|
3591
3708
|
try {
|
|
3592
|
-
const raw = await
|
|
3709
|
+
const raw = await readFile4(filePath, "utf8");
|
|
3593
3710
|
const parsed = JSON.parse(raw);
|
|
3594
3711
|
return parsed.approvals?.some((entry) => entry.approvalId === approvalId) ?? false;
|
|
3595
3712
|
} catch {
|
|
@@ -3600,7 +3717,7 @@ async function findApprovalRepoRoots(params) {
|
|
|
3600
3717
|
const matches = [];
|
|
3601
3718
|
const seen = /* @__PURE__ */ new Set();
|
|
3602
3719
|
for (const candidate of params.candidateRepoRoots) {
|
|
3603
|
-
const repoRoot =
|
|
3720
|
+
const repoRoot = path12.resolve(candidate);
|
|
3604
3721
|
if (seen.has(repoRoot)) {
|
|
3605
3722
|
continue;
|
|
3606
3723
|
}
|
|
@@ -3632,15 +3749,16 @@ function formatAmbiguousApprovalRepoMessage(repoRoots) {
|
|
|
3632
3749
|
|
|
3633
3750
|
// src/adapters/cursor/runtime-entry.ts
|
|
3634
3751
|
init_config();
|
|
3752
|
+
init_repo_config_trust();
|
|
3635
3753
|
init_cursor();
|
|
3636
3754
|
|
|
3637
3755
|
// src/adapters/shared/gate-runtime.ts
|
|
3638
3756
|
init_approval();
|
|
3639
3757
|
init_approval_replay();
|
|
3640
3758
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
3641
|
-
import { existsSync as
|
|
3642
|
-
import { mkdir as
|
|
3643
|
-
import
|
|
3759
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
3760
|
+
import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile11 } from "node:fs/promises";
|
|
3761
|
+
import path64 from "node:path";
|
|
3644
3762
|
|
|
3645
3763
|
// src/core/approval-service.ts
|
|
3646
3764
|
init_config_io();
|
|
@@ -3650,47 +3768,35 @@ init_approval_replay();
|
|
|
3650
3768
|
// src/core/approval-token.ts
|
|
3651
3769
|
init_config();
|
|
3652
3770
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
3653
|
-
import { existsSync as
|
|
3654
|
-
import { mkdir as
|
|
3655
|
-
import
|
|
3656
|
-
function base64UrlEncode(value) {
|
|
3657
|
-
return Buffer.from(value, "utf8").toString("base64url");
|
|
3658
|
-
}
|
|
3771
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3772
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile2 } from "node:fs/promises";
|
|
3773
|
+
import path13 from "node:path";
|
|
3659
3774
|
function base64UrlDecode(value) {
|
|
3660
3775
|
return Buffer.from(value, "base64url").toString("utf8");
|
|
3661
3776
|
}
|
|
3662
3777
|
function approvalSigningKeyPath(controlPlaneDir = defaultControlPlaneDir()) {
|
|
3663
|
-
return
|
|
3778
|
+
return path13.join(controlPlaneDir, "approval-signing.key");
|
|
3664
3779
|
}
|
|
3665
3780
|
async function loadOrCreateApprovalSigningKey(controlPlaneDir = defaultControlPlaneDir()) {
|
|
3666
3781
|
const keyPath = approvalSigningKeyPath(controlPlaneDir);
|
|
3667
|
-
if (
|
|
3668
|
-
return
|
|
3782
|
+
if (existsSync4(keyPath)) {
|
|
3783
|
+
return readFile5(keyPath);
|
|
3669
3784
|
}
|
|
3670
|
-
await
|
|
3785
|
+
await mkdir4(controlPlaneDir, { recursive: true });
|
|
3671
3786
|
const key = randomBytes(32);
|
|
3672
3787
|
await writeFile2(keyPath, key, { mode: 384 });
|
|
3673
3788
|
return key;
|
|
3674
3789
|
}
|
|
3675
|
-
function signPayload(payload, key) {
|
|
3676
|
-
const body = base64UrlEncode(JSON.stringify(payload));
|
|
3677
|
-
const signature = createHmac("sha256", key).update(body).digest("base64url");
|
|
3678
|
-
return `${body}.${signature}`;
|
|
3679
|
-
}
|
|
3680
|
-
async function issueApprovalToken(payload, controlPlaneDir = defaultControlPlaneDir()) {
|
|
3681
|
-
const key = await loadOrCreateApprovalSigningKey(controlPlaneDir);
|
|
3682
|
-
return signPayload(payload, key);
|
|
3683
|
-
}
|
|
3684
3790
|
async function verifyApprovalToken(token, controlPlaneDir = defaultControlPlaneDir()) {
|
|
3685
3791
|
const [body, signature] = token.split(".");
|
|
3686
3792
|
if (!body || !signature) {
|
|
3687
3793
|
return null;
|
|
3688
3794
|
}
|
|
3689
3795
|
const keyPath = approvalSigningKeyPath(controlPlaneDir);
|
|
3690
|
-
if (!
|
|
3796
|
+
if (!existsSync4(keyPath)) {
|
|
3691
3797
|
return null;
|
|
3692
3798
|
}
|
|
3693
|
-
const key = await
|
|
3799
|
+
const key = await readFile5(keyPath);
|
|
3694
3800
|
const expected = createHmac("sha256", key).update(body).digest("base64url");
|
|
3695
3801
|
const actualBuffer = Buffer.from(signature);
|
|
3696
3802
|
const expectedBuffer = Buffer.from(expected);
|
|
@@ -3897,11 +4003,11 @@ init_approval_v3();
|
|
|
3897
4003
|
|
|
3898
4004
|
// src/core/capability/boundary-attestation-sign.ts
|
|
3899
4005
|
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
3900
|
-
import { access, readFile as
|
|
4006
|
+
import { access, readFile as readFile6 } from "node:fs/promises";
|
|
3901
4007
|
init_fingerprint2();
|
|
3902
4008
|
|
|
3903
4009
|
// src/core/capability/attestation.ts
|
|
3904
|
-
import
|
|
4010
|
+
import path14 from "node:path";
|
|
3905
4011
|
var BOUNDARY_ATTESTATION_VERSION = 1;
|
|
3906
4012
|
var CONTAINED_EXECUTION_ATTESTATION_VERSION = 1;
|
|
3907
4013
|
var KNOWN_DRIVERS = /* @__PURE__ */ new Set([
|
|
@@ -3969,7 +4075,7 @@ function validateContainedExecutionAttestation(value) {
|
|
|
3969
4075
|
const record = value;
|
|
3970
4076
|
const dockerSubstrate = record.dockerSubstrate;
|
|
3971
4077
|
const dockerConfiguration = record.dockerConfiguration;
|
|
3972
|
-
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" || !
|
|
4078
|
+
if (record.version !== CONTAINED_EXECUTION_ATTESTATION_VERSION || typeof record.imageId !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(record.imageId) || typeof record.imageReference !== "string" || !record.imageReference || /[\0\n\r]/.test(record.imageReference) || record.networkNone !== true || record.isolatesWorkspaceMirror !== true || record.readOnlyRoot !== true || record.sanitizedEnvironment !== true || !isRecord(dockerSubstrate) || typeof dockerSubstrate.binaryPath !== "string" || !path14.isAbsolute(dockerSubstrate.binaryPath) || /[\0\n\r]/.test(dockerSubstrate.binaryPath) || typeof dockerSubstrate.binarySha256 !== "string" || !/^[a-f0-9]{64}$/.test(dockerSubstrate.binarySha256) || typeof dockerSubstrate.endpoint !== "string" || !dockerSubstrate.endpoint.startsWith("unix:///") || !path14.isAbsolute(dockerSubstrate.endpoint.slice("unix://".length)) || /[\0\n\r]/.test(dockerSubstrate.endpoint) || typeof dockerSubstrate.daemonId !== "string" || !dockerSubstrate.daemonId || /[\0\n\r]/.test(dockerSubstrate.daemonId) || !isRecord(dockerConfiguration) || typeof dockerConfiguration.executable !== "string" || !path14.isAbsolute(dockerConfiguration.executable) || /[\0\n\r]/.test(dockerConfiguration.executable) || typeof dockerConfiguration.host !== "string" || !dockerConfiguration.host.startsWith("unix:///") || !path14.isAbsolute(dockerConfiguration.host.slice("unix://".length)) || /[\0\n\r]/.test(dockerConfiguration.host) || typeof record.user !== "string" || !/^\d+:\d+$/.test(record.user) || record.entrypoint !== "/bin/sh" || record.capDropAll !== true || record.noNewPrivileges !== true || record.logDriver !== "none" || record.proxyEnvironment !== "neutralized-empty" || typeof record.probedAt !== "string" || typeof record.expiresAt !== "string") {
|
|
3973
4079
|
return false;
|
|
3974
4080
|
}
|
|
3975
4081
|
const probedAt = Date.parse(record.probedAt);
|
|
@@ -4039,7 +4145,7 @@ async function verifySignedBoundaryAttestation(params) {
|
|
|
4039
4145
|
return record.attestation;
|
|
4040
4146
|
}
|
|
4041
4147
|
async function readSignedAttestationFile(filePath) {
|
|
4042
|
-
return JSON.parse(await
|
|
4148
|
+
return JSON.parse(await readFile6(filePath, "utf8"));
|
|
4043
4149
|
}
|
|
4044
4150
|
|
|
4045
4151
|
// src/core/capability/boundary-egress.ts
|
|
@@ -4208,13 +4314,13 @@ async function runWithBoundaryRunnable(target, params) {
|
|
|
4208
4314
|
}
|
|
4209
4315
|
|
|
4210
4316
|
// src/core/capability/boundary-session.ts
|
|
4211
|
-
import
|
|
4317
|
+
import path25 from "node:path";
|
|
4212
4318
|
|
|
4213
4319
|
// src/services/egress-service.ts
|
|
4214
|
-
import { existsSync as
|
|
4215
|
-
import { mkdir as
|
|
4320
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
|
|
4321
|
+
import { mkdir as mkdir5, readFile as readFile7, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
|
|
4216
4322
|
import net from "node:net";
|
|
4217
|
-
import
|
|
4323
|
+
import path15 from "node:path";
|
|
4218
4324
|
init_config_io();
|
|
4219
4325
|
init_config();
|
|
4220
4326
|
|
|
@@ -4227,16 +4333,16 @@ function egressStatePaths(repoRoot, config) {
|
|
|
4227
4333
|
const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
|
|
4228
4334
|
return {
|
|
4229
4335
|
stateDir,
|
|
4230
|
-
pidPath:
|
|
4231
|
-
statusPath:
|
|
4336
|
+
pidPath: path15.join(stateDir, "egress-proxy.pid"),
|
|
4337
|
+
statusPath: path15.join(stateDir, "egress-proxy.json")
|
|
4232
4338
|
};
|
|
4233
4339
|
}
|
|
4234
4340
|
async function readStatusFile(statusPath) {
|
|
4235
|
-
if (!
|
|
4341
|
+
if (!existsSync5(statusPath)) {
|
|
4236
4342
|
return null;
|
|
4237
4343
|
}
|
|
4238
4344
|
try {
|
|
4239
|
-
const raw = JSON.parse(await
|
|
4345
|
+
const raw = JSON.parse(await readFile7(statusPath, "utf8"));
|
|
4240
4346
|
if (typeof raw.pid !== "number") {
|
|
4241
4347
|
return null;
|
|
4242
4348
|
}
|
|
@@ -4254,9 +4360,9 @@ async function readStatusFile(statusPath) {
|
|
|
4254
4360
|
async function isPortOpen(host, port) {
|
|
4255
4361
|
return new Promise((resolve) => {
|
|
4256
4362
|
const socket = net.createConnection({ host, port });
|
|
4257
|
-
const finish = (
|
|
4363
|
+
const finish = (open7) => {
|
|
4258
4364
|
socket.destroy();
|
|
4259
|
-
resolve(
|
|
4365
|
+
resolve(open7);
|
|
4260
4366
|
};
|
|
4261
4367
|
socket.setTimeout(300);
|
|
4262
4368
|
socket.on("connect", () => finish(true));
|
|
@@ -4267,7 +4373,7 @@ async function isPortOpen(host, port) {
|
|
|
4267
4373
|
async function resolveLiveEgressStatus(repoRoot, config) {
|
|
4268
4374
|
const { statusPath } = egressStatePaths(repoRoot, config);
|
|
4269
4375
|
const statusCandidates = [statusPath];
|
|
4270
|
-
const controlPlaneStatus =
|
|
4376
|
+
const controlPlaneStatus = path15.join(configuredControlPlaneDir(config), "egress-proxy.json");
|
|
4271
4377
|
if (!statusCandidates.includes(controlPlaneStatus)) {
|
|
4272
4378
|
statusCandidates.push(controlPlaneStatus);
|
|
4273
4379
|
}
|
|
@@ -4293,7 +4399,7 @@ function isProcessAlive(pid) {
|
|
|
4293
4399
|
}
|
|
4294
4400
|
}
|
|
4295
4401
|
async function egressStatus(options = {}) {
|
|
4296
|
-
const repoRoot =
|
|
4402
|
+
const repoRoot = path15.resolve(options.targetDir ?? process.cwd());
|
|
4297
4403
|
const config = await loadConfigFile(repoRoot);
|
|
4298
4404
|
const { status, host, port, portOccupied } = await resolveLiveEgressStatus(repoRoot, config);
|
|
4299
4405
|
const ownedRunning = Boolean(status);
|
|
@@ -4324,7 +4430,7 @@ init_config();
|
|
|
4324
4430
|
import { createHash as createHash8, randomUUID as randomUUID2 } from "node:crypto";
|
|
4325
4431
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
4326
4432
|
import { access as access2, constants as constants2, lstat as lstat4, mkdtemp as mkdtemp2, realpath as realpath2, rm as rm2 } from "node:fs/promises";
|
|
4327
|
-
import
|
|
4433
|
+
import path20 from "node:path";
|
|
4328
4434
|
init_fingerprint2();
|
|
4329
4435
|
init_path_utils();
|
|
4330
4436
|
|
|
@@ -4503,7 +4609,7 @@ function runProcessWithBoundedOutput(file, args, options, timeoutMs, outputPolic
|
|
|
4503
4609
|
}
|
|
4504
4610
|
|
|
4505
4611
|
// src/core/contained-execution/docker-policy.ts
|
|
4506
|
-
import
|
|
4612
|
+
import path16 from "node:path";
|
|
4507
4613
|
|
|
4508
4614
|
// src/core/contained-execution/policy.ts
|
|
4509
4615
|
var CONTAINED_EXECUTION_APPROVAL_FALLBACK_REASONS = [
|
|
@@ -4632,7 +4738,7 @@ var PROXY_ENV_NAMES = [
|
|
|
4632
4738
|
var IMAGE_ID_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
4633
4739
|
var SAFE_CONTAINER_NAME = /^belay-contained-[0-9a-f-]{36}$/;
|
|
4634
4740
|
function assertSafeDockerPath(value, code) {
|
|
4635
|
-
if (!
|
|
4741
|
+
if (!path16.isAbsolute(value) || /[\0\n\r,]/.test(value)) {
|
|
4636
4742
|
throw new ContainedExecutionFailureError(code);
|
|
4637
4743
|
}
|
|
4638
4744
|
}
|
|
@@ -4711,11 +4817,11 @@ init_path_utils();
|
|
|
4711
4817
|
import { createHash as createHash7 } from "node:crypto";
|
|
4712
4818
|
import { constants as fsConstants } from "node:fs";
|
|
4713
4819
|
import {
|
|
4714
|
-
chmod as
|
|
4820
|
+
chmod as chmod3,
|
|
4715
4821
|
lstat as lstat3,
|
|
4716
|
-
mkdir as
|
|
4822
|
+
mkdir as mkdir6,
|
|
4717
4823
|
mkdtemp,
|
|
4718
|
-
open as
|
|
4824
|
+
open as open4,
|
|
4719
4825
|
opendir,
|
|
4720
4826
|
readlink as readlink2,
|
|
4721
4827
|
realpath,
|
|
@@ -4723,41 +4829,41 @@ import {
|
|
|
4723
4829
|
symlink
|
|
4724
4830
|
} from "node:fs/promises";
|
|
4725
4831
|
import os from "node:os";
|
|
4726
|
-
import
|
|
4832
|
+
import path19 from "node:path";
|
|
4727
4833
|
|
|
4728
4834
|
// src/core/transactional/file-tree.ts
|
|
4729
4835
|
import { createHash as createHash6 } from "node:crypto";
|
|
4730
4836
|
import { lstat as lstat2, readdir } from "node:fs/promises";
|
|
4731
|
-
import
|
|
4837
|
+
import path18 from "node:path";
|
|
4732
4838
|
|
|
4733
4839
|
// src/core/transactional/file-tree-path.ts
|
|
4734
4840
|
init_path_utils();
|
|
4735
|
-
import
|
|
4841
|
+
import path17 from "node:path";
|
|
4736
4842
|
var FILE_CHECKPOINT_PATH_ESCAPE = "file_checkpoint_path_escape";
|
|
4737
4843
|
function validateRelativePath(relativePath) {
|
|
4738
4844
|
if (!relativePath || relativePath.includes("\0")) {
|
|
4739
4845
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
4740
4846
|
}
|
|
4741
|
-
if (
|
|
4847
|
+
if (path17.isAbsolute(relativePath)) {
|
|
4742
4848
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
4743
4849
|
}
|
|
4744
4850
|
if (isPathOutsideRoot(relativePath)) {
|
|
4745
4851
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
4746
4852
|
}
|
|
4747
|
-
const normalized =
|
|
4748
|
-
if (normalized === ".." || normalized.startsWith(`..${
|
|
4853
|
+
const normalized = path17.normalize(relativePath);
|
|
4854
|
+
if (normalized === ".." || normalized.startsWith(`..${path17.sep}`)) {
|
|
4749
4855
|
throw new Error(FILE_CHECKPOINT_PATH_ESCAPE);
|
|
4750
4856
|
}
|
|
4751
4857
|
}
|
|
4752
4858
|
function joinRelativePath(root, relativePath) {
|
|
4753
4859
|
validateRelativePath(relativePath);
|
|
4754
|
-
return
|
|
4860
|
+
return path17.join(canonicalPath(root), relativePath);
|
|
4755
4861
|
}
|
|
4756
4862
|
function isRootGitMetadataPath(relativePath) {
|
|
4757
|
-
return relativePath === ".git" || relativePath.startsWith(`.git${
|
|
4863
|
+
return relativePath === ".git" || relativePath.startsWith(`.git${path17.sep}`);
|
|
4758
4864
|
}
|
|
4759
4865
|
function isNestedGitPath(relativePath) {
|
|
4760
|
-
const segments = relativePath.split(
|
|
4866
|
+
const segments = relativePath.split(path17.sep).filter(Boolean);
|
|
4761
4867
|
if (segments.length === 0) {
|
|
4762
4868
|
return false;
|
|
4763
4869
|
}
|
|
@@ -4924,7 +5030,7 @@ async function readPresentNode(absolutePath, counters) {
|
|
|
4924
5030
|
async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters, quotas, deadlineMs, entries) {
|
|
4925
5031
|
assertWithinDeadline(deadlineMs);
|
|
4926
5032
|
assertWithinQuotas(counters, quotas);
|
|
4927
|
-
const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) :
|
|
5033
|
+
const absoluteDir = relativeDir ? joinRelativePath(resourceRoot, relativeDir) : path18.resolve(resourceRoot);
|
|
4928
5034
|
const dirInfo = await lstat2(absoluteDir);
|
|
4929
5035
|
if (!dirInfo.isDirectory()) {
|
|
4930
5036
|
throw new Error(FILE_CHECKPOINT_UNSUPPORTED_NODE);
|
|
@@ -4950,7 +5056,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
|
|
|
4950
5056
|
const names = await readdir(absoluteDir);
|
|
4951
5057
|
for (const name of names) {
|
|
4952
5058
|
assertWithinDeadline(deadlineMs);
|
|
4953
|
-
const childRelative = relativeDir ?
|
|
5059
|
+
const childRelative = relativeDir ? path18.join(relativeDir, name) : name;
|
|
4954
5060
|
validateRelativePath(childRelative);
|
|
4955
5061
|
if (isNestedGitPath(childRelative)) {
|
|
4956
5062
|
throw new Error(FILE_CHECKPOINT_NESTED_REPOSITORY);
|
|
@@ -4958,7 +5064,7 @@ async function walkDirectory(resourceRoot, relativeDir, excludedRoots, counters,
|
|
|
4958
5064
|
if (isExcludedTreePath(childRelative, excludedRoots, resourceRoot)) {
|
|
4959
5065
|
continue;
|
|
4960
5066
|
}
|
|
4961
|
-
const childAbsolute =
|
|
5067
|
+
const childAbsolute = path18.join(absoluteDir, name);
|
|
4962
5068
|
const childInfo = await lstat2(childAbsolute);
|
|
4963
5069
|
if (childInfo.isDirectory() && !childInfo.isSymbolicLink()) {
|
|
4964
5070
|
await walkDirectory(
|
|
@@ -5069,16 +5175,16 @@ function validateContainedExecutionMirrorLease(handle, expected) {
|
|
|
5069
5175
|
);
|
|
5070
5176
|
}
|
|
5071
5177
|
var productionDependencies = {
|
|
5072
|
-
makeTempRoot: () => mkdtemp(
|
|
5178
|
+
makeTempRoot: () => mkdtemp(path19.join(os.tmpdir(), "belay-contained-mirror-")),
|
|
5073
5179
|
removeRoot: (root) => rm(root, { recursive: true, force: true }),
|
|
5074
5180
|
now: () => Date.now()
|
|
5075
5181
|
};
|
|
5076
5182
|
function isAtOrWithin(root, target) {
|
|
5077
|
-
const relative =
|
|
5078
|
-
return relative === "" || !
|
|
5183
|
+
const relative = path19.relative(root, target);
|
|
5184
|
+
return relative === "" || !path19.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path19.sep}`);
|
|
5079
5185
|
}
|
|
5080
5186
|
function isGitMetadataRelativePath(relativePath) {
|
|
5081
|
-
return relativePath.split(
|
|
5187
|
+
return relativePath.split(path19.sep).some((segment) => segment.toLowerCase() === ".git");
|
|
5082
5188
|
}
|
|
5083
5189
|
function identityFromStats(stats) {
|
|
5084
5190
|
return {
|
|
@@ -5160,7 +5266,7 @@ async function readStableFile(absolutePath, context) {
|
|
|
5160
5266
|
if (before.nlink > 1n) {
|
|
5161
5267
|
throw new Error(FILE_CHECKPOINT_HARDLINK_UNSUPPORTED);
|
|
5162
5268
|
}
|
|
5163
|
-
const source = await
|
|
5269
|
+
const source = await open4(absolutePath, safeReadFlags());
|
|
5164
5270
|
try {
|
|
5165
5271
|
const opened = await source.stat({ bigint: true });
|
|
5166
5272
|
assertRegularSingleLinkBigInt(opened);
|
|
@@ -5215,7 +5321,7 @@ function addMetadataRoots(context, directoryPath) {
|
|
|
5215
5321
|
}
|
|
5216
5322
|
}
|
|
5217
5323
|
function pathMatchesRoots(absolutePath, roots) {
|
|
5218
|
-
const lexical =
|
|
5324
|
+
const lexical = path19.resolve(absolutePath);
|
|
5219
5325
|
const canonical = canonicalPath(absolutePath);
|
|
5220
5326
|
for (const root of roots) {
|
|
5221
5327
|
if (isAtOrWithin(root, lexical) || isAtOrWithin(root, canonical)) {
|
|
@@ -5225,7 +5331,7 @@ function pathMatchesRoots(absolutePath, roots) {
|
|
|
5225
5331
|
return false;
|
|
5226
5332
|
}
|
|
5227
5333
|
function pathLexicallyMatchesRoots(absolutePath, roots) {
|
|
5228
|
-
const lexical =
|
|
5334
|
+
const lexical = path19.resolve(absolutePath);
|
|
5229
5335
|
for (const root of roots) {
|
|
5230
5336
|
if (isAtOrWithin(root, lexical)) {
|
|
5231
5337
|
return true;
|
|
@@ -5246,11 +5352,11 @@ async function readSafeSymlink(absolutePath, context) {
|
|
|
5246
5352
|
}
|
|
5247
5353
|
const identity = identityFromStats(before);
|
|
5248
5354
|
const target = await readlink2(absolutePath);
|
|
5249
|
-
if (
|
|
5355
|
+
if (path19.isAbsolute(target)) {
|
|
5250
5356
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
|
|
5251
5357
|
}
|
|
5252
|
-
const lexicalTarget =
|
|
5253
|
-
if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(
|
|
5358
|
+
const lexicalTarget = path19.resolve(path19.dirname(absolutePath), target);
|
|
5359
|
+
if (!isAtOrWithin(context.sourceRoot, lexicalTarget) || isGitMetadataRelativePath(path19.relative(context.sourceRoot, lexicalTarget)) || pathMatchesRoots(lexicalTarget, context.protectedRoots) || pathMatchesRoots(lexicalTarget, context.metadataRoots)) {
|
|
5254
5360
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_UNSAFE_SYMLINK);
|
|
5255
5361
|
}
|
|
5256
5362
|
let resolvedTarget;
|
|
@@ -5286,7 +5392,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
|
|
|
5286
5392
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5287
5393
|
}
|
|
5288
5394
|
const directoryFlags = safeReadFlags() | (fsConstants.O_DIRECTORY ?? 0);
|
|
5289
|
-
const directory = await
|
|
5395
|
+
const directory = await open4(absoluteDirectory, directoryFlags);
|
|
5290
5396
|
try {
|
|
5291
5397
|
const opened = await directory.stat({ bigint: true });
|
|
5292
5398
|
if (!opened.isDirectory() || !identitiesEqual(beforeIdentity, identityFromStats(opened))) {
|
|
@@ -5295,7 +5401,7 @@ async function walkDirectory2(relativeDirectory, entries, context) {
|
|
|
5295
5401
|
const entriesDirectory = await opendir(absoluteDirectory, { bufferSize: 32 });
|
|
5296
5402
|
for await (const directoryEntry of entriesDirectory) {
|
|
5297
5403
|
assertDeadline(context.deadlineMs, context.now);
|
|
5298
|
-
const relativePath = relativeDirectory ?
|
|
5404
|
+
const relativePath = relativeDirectory ? path19.join(relativeDirectory, directoryEntry.name) : directoryEntry.name;
|
|
5299
5405
|
const absolutePath = joinRelativePath(context.sourceRoot, relativePath);
|
|
5300
5406
|
const info = await lstat3(absolutePath);
|
|
5301
5407
|
if (info.isSymbolicLink()) {
|
|
@@ -5379,7 +5485,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5379
5485
|
}
|
|
5380
5486
|
assertEntryIdentity(entry, identityFromStats(before));
|
|
5381
5487
|
await context.beforeCopyOpen?.(sourcePath);
|
|
5382
|
-
const source = await
|
|
5488
|
+
const source = await open4(sourcePath, safeReadFlags());
|
|
5383
5489
|
let destination;
|
|
5384
5490
|
try {
|
|
5385
5491
|
const opened = await source.stat({ bigint: true });
|
|
@@ -5389,8 +5495,8 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5389
5495
|
if (entry.node.kind !== "file") {
|
|
5390
5496
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5391
5497
|
}
|
|
5392
|
-
await
|
|
5393
|
-
destination = await
|
|
5498
|
+
await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
|
|
5499
|
+
destination = await open4(
|
|
5394
5500
|
destinationPath,
|
|
5395
5501
|
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
|
|
5396
5502
|
safePermissionMode(opened.mode)
|
|
@@ -5432,7 +5538,7 @@ async function copyStableRegularFile(sourcePath, destinationPath, entry, context
|
|
|
5432
5538
|
if (entry.node.kind !== "file") {
|
|
5433
5539
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5434
5540
|
}
|
|
5435
|
-
await
|
|
5541
|
+
await chmod3(destinationPath, safePermissionMode(entry.node.mode));
|
|
5436
5542
|
}
|
|
5437
5543
|
async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, context) {
|
|
5438
5544
|
const directoryEntries = [];
|
|
@@ -5441,7 +5547,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
5441
5547
|
const sourcePath = joinRelativePath(sourceRoot, entry.relativePath);
|
|
5442
5548
|
const destinationPath = joinRelativePath(destinationRoot, entry.relativePath);
|
|
5443
5549
|
if (entry.node.kind === "directory") {
|
|
5444
|
-
await
|
|
5550
|
+
await mkdir6(destinationPath, { recursive: true, mode: 448 });
|
|
5445
5551
|
directoryEntries.push(entry);
|
|
5446
5552
|
continue;
|
|
5447
5553
|
}
|
|
@@ -5453,7 +5559,7 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
5453
5559
|
if (current !== entry.node.target) {
|
|
5454
5560
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5455
5561
|
}
|
|
5456
|
-
await
|
|
5562
|
+
await mkdir6(path19.dirname(destinationPath), { recursive: true, mode: 448 });
|
|
5457
5563
|
await symlink(entry.node.target, destinationPath);
|
|
5458
5564
|
continue;
|
|
5459
5565
|
}
|
|
@@ -5463,12 +5569,12 @@ async function materializeSnapshot(sourceRoot, destinationRoot, snapshot, contex
|
|
|
5463
5569
|
if (entry.node.kind !== "directory") {
|
|
5464
5570
|
throw new ContainedExecutionFailureError(CONTAINED_EXECUTION_SOURCE_CHANGED);
|
|
5465
5571
|
}
|
|
5466
|
-
await
|
|
5572
|
+
await chmod3(
|
|
5467
5573
|
joinRelativePath(destinationRoot, entry.relativePath),
|
|
5468
5574
|
safePermissionMode(entry.node.mode)
|
|
5469
5575
|
);
|
|
5470
5576
|
}
|
|
5471
|
-
await
|
|
5577
|
+
await chmod3(destinationRoot, 448);
|
|
5472
5578
|
}
|
|
5473
5579
|
async function assertStableCopiedSnapshot(sourceRoot, destinationRoot, protectedRoots, limits, deadlineMs, now, before) {
|
|
5474
5580
|
const [after, copied] = await Promise.all([
|
|
@@ -5513,7 +5619,7 @@ async function cleanupOwnedRoot(root, dependencies) {
|
|
|
5513
5619
|
}
|
|
5514
5620
|
async function prepareWithDependencies(options, dependencies) {
|
|
5515
5621
|
validateOptions(options);
|
|
5516
|
-
const guestWorkspacePath =
|
|
5622
|
+
const guestWorkspacePath = path19.resolve(options.sourceRoot);
|
|
5517
5623
|
const sourceRoot = canonicalPath(options.sourceRoot);
|
|
5518
5624
|
const protectedRoots = options.controlPlaneRoots.map((root) => canonicalPath(root));
|
|
5519
5625
|
if (pathMatchesRoots(sourceRoot, protectedRoots)) {
|
|
@@ -5521,7 +5627,7 @@ async function prepareWithDependencies(options, dependencies) {
|
|
|
5521
5627
|
}
|
|
5522
5628
|
const guestRoot = await dependencies.makeTempRoot();
|
|
5523
5629
|
try {
|
|
5524
|
-
await
|
|
5630
|
+
await chmod3(guestRoot, 448);
|
|
5525
5631
|
const deadlineMs = dependencies.now() + options.limits.prepareTimeoutMs;
|
|
5526
5632
|
const before = await buildSafeMirrorSnapshot(
|
|
5527
5633
|
sourceRoot,
|
|
@@ -5549,7 +5655,7 @@ async function prepareWithDependencies(options, dependencies) {
|
|
|
5549
5655
|
dependencies.now,
|
|
5550
5656
|
before
|
|
5551
5657
|
);
|
|
5552
|
-
await
|
|
5658
|
+
await chmod3(guestRoot, 448);
|
|
5553
5659
|
const lease = {
|
|
5554
5660
|
sourceRoot,
|
|
5555
5661
|
hostMirrorRoot: guestRoot,
|
|
@@ -5683,14 +5789,14 @@ async function digestFile(file) {
|
|
|
5683
5789
|
return hash.digest("hex");
|
|
5684
5790
|
}
|
|
5685
5791
|
async function resolveConfiguredDockerSubstrate(params) {
|
|
5686
|
-
if (!
|
|
5792
|
+
if (!path20.isAbsolute(params.executable) || /[\0\n\r]/.test(params.executable)) {
|
|
5687
5793
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_binary_invalid");
|
|
5688
5794
|
}
|
|
5689
5795
|
if (!params.host.startsWith("unix:///") || /[\0\n\r]/.test(params.host)) {
|
|
5690
5796
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
|
|
5691
5797
|
}
|
|
5692
5798
|
const configuredSocket = params.host.slice("unix://".length);
|
|
5693
|
-
if (!
|
|
5799
|
+
if (!path20.isAbsolute(configuredSocket)) {
|
|
5694
5800
|
throw new ContainedDockerBoundaryUnavailableError("contained_execution_docker_host_invalid");
|
|
5695
5801
|
}
|
|
5696
5802
|
let binaryPath;
|
|
@@ -5951,7 +6057,7 @@ async function validatedGuestCwd(params) {
|
|
|
5951
6057
|
const requiredExclusions = [
|
|
5952
6058
|
...new Set([params.controlPlaneDir, ...params.protectedRoots].map(canonicalPath))
|
|
5953
6059
|
];
|
|
5954
|
-
if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !==
|
|
6060
|
+
if (params.mirror.backend !== "file_copy" || params.mirror.guestWorkspacePath !== path20.resolve(params.repoRoot) || !path20.isAbsolute(params.guestCwd) || !pathWithinRoot(params.mirror.guestWorkspacePath, params.guestCwd))
|
|
5955
6061
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
5956
6062
|
const resolvedRoot = await realpath2(params.mirror.hostMirrorRoot);
|
|
5957
6063
|
const protectedRoots = [canonicalPath(params.repoRoot), ...requiredExclusions];
|
|
@@ -5963,10 +6069,10 @@ async function validatedGuestCwd(params) {
|
|
|
5963
6069
|
protectedRoots: requiredExclusions
|
|
5964
6070
|
}))
|
|
5965
6071
|
throw new ContainedExecutionFailureError("contained_execution_invalid_mirror_lease");
|
|
5966
|
-
const relative =
|
|
6072
|
+
const relative = path20.relative(params.mirror.guestWorkspacePath, path20.resolve(params.guestCwd));
|
|
5967
6073
|
let resolvedCwd;
|
|
5968
6074
|
try {
|
|
5969
|
-
resolvedCwd = await realpath2(
|
|
6075
|
+
resolvedCwd = await realpath2(path20.join(resolvedRoot, relative));
|
|
5970
6076
|
} catch {
|
|
5971
6077
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
5972
6078
|
}
|
|
@@ -5974,7 +6080,7 @@ async function validatedGuestCwd(params) {
|
|
|
5974
6080
|
if (!info.isDirectory() || !pathWithinRoot(resolvedRoot, resolvedCwd)) {
|
|
5975
6081
|
throw new ContainedExecutionFailureError("contained_execution_invalid_cwd");
|
|
5976
6082
|
}
|
|
5977
|
-
return
|
|
6083
|
+
return path20.join(params.mirror.guestWorkspacePath, path20.relative(resolvedRoot, resolvedCwd));
|
|
5978
6084
|
}
|
|
5979
6085
|
function exactKeys(value, allowed) {
|
|
5980
6086
|
const names = new Set(allowed);
|
|
@@ -6201,19 +6307,19 @@ async function executeContainedDocker(params) {
|
|
|
6201
6307
|
init_path_utils();
|
|
6202
6308
|
import { spawn as spawn3 } from "node:child_process";
|
|
6203
6309
|
import os3 from "node:os";
|
|
6204
|
-
import
|
|
6310
|
+
import path22 from "node:path";
|
|
6205
6311
|
|
|
6206
6312
|
// src/core/transactional/apply-observed-changes.ts
|
|
6207
|
-
import { copyFile, lstat as lstat5, mkdir as
|
|
6313
|
+
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";
|
|
6208
6314
|
import os2 from "node:os";
|
|
6209
|
-
import
|
|
6315
|
+
import path21 from "node:path";
|
|
6210
6316
|
var TRANSACTIONAL_APPLY_TOCTOU = "transactional_apply_toctou";
|
|
6211
6317
|
var TRANSACTIONAL_APPLY_CONFLICT = "transactional_apply_conflict";
|
|
6212
6318
|
var TRANSACTIONAL_APPLY_ROLLBACK_FAILED = "transactional_apply_rollback_failed";
|
|
6213
6319
|
async function chmodSafe(target, mode) {
|
|
6214
6320
|
try {
|
|
6215
|
-
const { chmod:
|
|
6216
|
-
await
|
|
6321
|
+
const { chmod: chmod5 } = await import("node:fs/promises");
|
|
6322
|
+
await chmod5(target, mode & 511);
|
|
6217
6323
|
} catch {
|
|
6218
6324
|
}
|
|
6219
6325
|
}
|
|
@@ -6247,13 +6353,13 @@ async function copyPathPreservingType(source, target) {
|
|
|
6247
6353
|
}
|
|
6248
6354
|
}
|
|
6249
6355
|
await removePathIfExists(target);
|
|
6250
|
-
await
|
|
6356
|
+
await mkdir7(path21.dirname(target), { recursive: true });
|
|
6251
6357
|
if (info.isSymbolicLink()) {
|
|
6252
6358
|
await symlink2(await readlink3(source), target);
|
|
6253
6359
|
return;
|
|
6254
6360
|
}
|
|
6255
6361
|
if (info.isDirectory() && !info.isSymbolicLink()) {
|
|
6256
|
-
await
|
|
6362
|
+
await mkdir7(target, { recursive: true, mode: info.mode & 511 });
|
|
6257
6363
|
return;
|
|
6258
6364
|
}
|
|
6259
6365
|
if (!info.isFile()) {
|
|
@@ -6263,12 +6369,12 @@ async function copyPathPreservingType(source, target) {
|
|
|
6263
6369
|
await chmodSafe(target, info.mode);
|
|
6264
6370
|
}
|
|
6265
6371
|
async function assertParentChainSafe(targetRoot, relativePath, plannedDirectories = /* @__PURE__ */ new Set()) {
|
|
6266
|
-
const segments = relativePath.split(
|
|
6372
|
+
const segments = relativePath.split(path21.sep).filter(Boolean);
|
|
6267
6373
|
if (segments.length <= 1) {
|
|
6268
6374
|
return;
|
|
6269
6375
|
}
|
|
6270
6376
|
for (let index = 1; index < segments.length; index++) {
|
|
6271
|
-
const prefix = segments.slice(0, index).join(
|
|
6377
|
+
const prefix = segments.slice(0, index).join(path21.sep);
|
|
6272
6378
|
const absolute = joinRelativePath(targetRoot, prefix);
|
|
6273
6379
|
let info;
|
|
6274
6380
|
try {
|
|
@@ -6288,8 +6394,8 @@ async function assertParentChainSafe(targetRoot, relativePath, plannedDirectorie
|
|
|
6288
6394
|
}
|
|
6289
6395
|
}
|
|
6290
6396
|
async function restorePathFromBackup(backupPath, target, rollbackRoot) {
|
|
6291
|
-
const stagingRoot = await mkdtemp3(
|
|
6292
|
-
const staged =
|
|
6397
|
+
const stagingRoot = await mkdtemp3(path21.join(rollbackRoot, "restore-"));
|
|
6398
|
+
const staged = path21.join(stagingRoot, "node");
|
|
6293
6399
|
try {
|
|
6294
6400
|
await copyPathPreservingType(backupPath, staged);
|
|
6295
6401
|
await copyPathPreservingType(staged, target);
|
|
@@ -6373,7 +6479,7 @@ async function applySingleChange(sourceRoot, targetRoot, change) {
|
|
|
6373
6479
|
if (change.before.kind !== "directory") {
|
|
6374
6480
|
await removePathIfExists(target);
|
|
6375
6481
|
}
|
|
6376
|
-
await
|
|
6482
|
+
await mkdir7(target, { recursive: true, mode: change.after.mode & 511 });
|
|
6377
6483
|
await chmodSafe(target, change.after.mode);
|
|
6378
6484
|
return;
|
|
6379
6485
|
}
|
|
@@ -6392,7 +6498,7 @@ async function applyObservedChanges(params) {
|
|
|
6392
6498
|
await assertParentChainSafe(targetRoot, change.relativePath, plannedDirectories);
|
|
6393
6499
|
await assertTargetMatches(targetRoot, change);
|
|
6394
6500
|
}
|
|
6395
|
-
const backupRoot = await mkdtemp3(
|
|
6501
|
+
const backupRoot = await mkdtemp3(path21.join(os2.tmpdir(), "belay-tx-rollback-"));
|
|
6396
6502
|
const rollbackActions = [];
|
|
6397
6503
|
let mutationAttempted = false;
|
|
6398
6504
|
let resourceIdentityChanged = false;
|
|
@@ -6411,12 +6517,12 @@ async function applyObservedChanges(params) {
|
|
|
6411
6517
|
targetExists = false;
|
|
6412
6518
|
}
|
|
6413
6519
|
if (targetExists) {
|
|
6414
|
-
const backupPath =
|
|
6520
|
+
const backupPath = path21.join(
|
|
6415
6521
|
backupRoot,
|
|
6416
6522
|
String(rollbackActions.length),
|
|
6417
6523
|
change.relativePath
|
|
6418
6524
|
);
|
|
6419
|
-
await
|
|
6525
|
+
await mkdir7(path21.dirname(backupPath), { recursive: true });
|
|
6420
6526
|
await copyPathPreservingType(target, backupPath);
|
|
6421
6527
|
await assertParentChainSafe(targetRoot, change.relativePath);
|
|
6422
6528
|
rollbackActions.push({
|
|
@@ -6515,7 +6621,7 @@ function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
|
|
|
6515
6621
|
if (ignoreRoots.length === 0) {
|
|
6516
6622
|
return false;
|
|
6517
6623
|
}
|
|
6518
|
-
const absolutePath = canonicalPath(
|
|
6624
|
+
const absolutePath = canonicalPath(path22.join(repoRoot, relativePath));
|
|
6519
6625
|
return ignoreRoots.some(
|
|
6520
6626
|
(root) => pathWithinRoot(root, absolutePath) || root === absolutePath || pathWithinRoot(absolutePath, root)
|
|
6521
6627
|
);
|
|
@@ -6553,7 +6659,7 @@ async function isDirtyWorktree(repoRoot, options) {
|
|
|
6553
6659
|
}
|
|
6554
6660
|
async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
6555
6661
|
const { mkdtemp: mkdtemp6, rm: rm10 } = await import("node:fs/promises");
|
|
6556
|
-
const worktreePath = await mkdtemp6(
|
|
6662
|
+
const worktreePath = await mkdtemp6(path22.join(os3.tmpdir(), "belay-tx-"));
|
|
6557
6663
|
await execGit(repoRoot, ["worktree", "add", "--detach", worktreePath, "HEAD"]);
|
|
6558
6664
|
return {
|
|
6559
6665
|
worktreePath,
|
|
@@ -6572,14 +6678,14 @@ async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
|
6572
6678
|
}
|
|
6573
6679
|
function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
|
|
6574
6680
|
const resolvedCwd = canonicalPath(cwd);
|
|
6575
|
-
const relative =
|
|
6576
|
-
if (isPathOutsideRoot(relative) ||
|
|
6681
|
+
const relative = path22.relative(canonicalPath(repoRoot), resolvedCwd);
|
|
6682
|
+
if (isPathOutsideRoot(relative) || path22.isAbsolute(relative)) {
|
|
6577
6683
|
return worktreePath;
|
|
6578
6684
|
}
|
|
6579
6685
|
if (relative === "") {
|
|
6580
6686
|
return worktreePath;
|
|
6581
6687
|
}
|
|
6582
|
-
return
|
|
6688
|
+
return path22.join(worktreePath, relative);
|
|
6583
6689
|
}
|
|
6584
6690
|
function runShellCommand(command, cwd, timeoutMs) {
|
|
6585
6691
|
return runProcessWithBoundedOutput(command, [], { cwd, shell: true, env: process.env }, timeoutMs);
|
|
@@ -6644,7 +6750,7 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
6644
6750
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
6645
6751
|
|
|
6646
6752
|
// src/core/capability/boundary-grant-materialize.ts
|
|
6647
|
-
import
|
|
6753
|
+
import path23 from "node:path";
|
|
6648
6754
|
|
|
6649
6755
|
// src/core/glob.ts
|
|
6650
6756
|
init_approval();
|
|
@@ -6804,7 +6910,7 @@ function grantMatchesRequest(grant, request) {
|
|
|
6804
6910
|
var BOUNDARY_GRANT_TTL_MS = 15 * 6e4;
|
|
6805
6911
|
var BOUNDARY_GRANT_ISSUER_CONTAINER = "boundary:container";
|
|
6806
6912
|
function resolveCapabilityPath(targetPath, cwd) {
|
|
6807
|
-
const joined =
|
|
6913
|
+
const joined = path23.isAbsolute(targetPath) ? targetPath : path23.join(cwd, targetPath);
|
|
6808
6914
|
return canonicalPath(joined);
|
|
6809
6915
|
}
|
|
6810
6916
|
function isPathWithinBoundaryMount(request) {
|
|
@@ -6879,7 +6985,7 @@ function materializeContainerBoundaryGrant(request, params) {
|
|
|
6879
6985
|
|
|
6880
6986
|
// src/core/capability/boundary-workspace-mount.ts
|
|
6881
6987
|
init_path_utils();
|
|
6882
|
-
import
|
|
6988
|
+
import path24 from "node:path";
|
|
6883
6989
|
var HOST_PATH_ENV_VARS = [
|
|
6884
6990
|
"BELAY_EGRESS_REPO_ROOT",
|
|
6885
6991
|
"BELAY_JUDGE_BROKER_REPO_ROOT",
|
|
@@ -6904,23 +7010,23 @@ function validateWorkspaceMount(mount) {
|
|
|
6904
7010
|
if (mount.cwdRelative.includes("\0")) {
|
|
6905
7011
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
6906
7012
|
}
|
|
6907
|
-
const normalizedRelative =
|
|
7013
|
+
const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
|
|
6908
7014
|
if (normalizedRelative === ".." || normalizedRelative.startsWith("../")) {
|
|
6909
7015
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
6910
7016
|
}
|
|
6911
|
-
if (
|
|
7017
|
+
if (path24.posix.isAbsolute(normalizedRelative)) {
|
|
6912
7018
|
throw new Error("boundary_workspace_mount_invalid_cwd");
|
|
6913
7019
|
}
|
|
6914
7020
|
}
|
|
6915
7021
|
function resolveGuestWorkdir(mount) {
|
|
6916
7022
|
validateWorkspaceMount(mount);
|
|
6917
7023
|
const guestTargetRoot = canonicalPath(mount.guestTargetRoot);
|
|
6918
|
-
const normalizedRelative =
|
|
7024
|
+
const normalizedRelative = path24.posix.normalize(mount.cwdRelative.replace(/\\/g, "/"));
|
|
6919
7025
|
if (normalizedRelative === "." || normalizedRelative === "") {
|
|
6920
7026
|
return guestTargetRoot;
|
|
6921
7027
|
}
|
|
6922
7028
|
const segments = normalizedRelative.split("/").filter(Boolean);
|
|
6923
|
-
return
|
|
7029
|
+
return path24.posix.join(guestTargetRoot, ...segments);
|
|
6924
7030
|
}
|
|
6925
7031
|
function buildWorkspaceMountSpec(mount) {
|
|
6926
7032
|
validateWorkspaceMount(mount);
|
|
@@ -7211,7 +7317,7 @@ function containedExecutionFresh(attestation, config, now = Date.now()) {
|
|
|
7211
7317
|
}
|
|
7212
7318
|
function boundaryAttestationPath(repoRoot, config) {
|
|
7213
7319
|
const rel = config.capability?.attestationRelPath ?? ".belay/attestation.json";
|
|
7214
|
-
return
|
|
7320
|
+
return path25.isAbsolute(rel) ? rel : path25.join(repoRoot, rel);
|
|
7215
7321
|
}
|
|
7216
7322
|
async function loadBoundaryAttestation(filePath, expectedRepoRoot, controlPlaneDir) {
|
|
7217
7323
|
try {
|
|
@@ -7316,7 +7422,7 @@ init_capability_request_hash();
|
|
|
7316
7422
|
|
|
7317
7423
|
// src/core/capability/gate-policy-shadow.ts
|
|
7318
7424
|
init_config();
|
|
7319
|
-
import
|
|
7425
|
+
import path31 from "node:path";
|
|
7320
7426
|
|
|
7321
7427
|
// src/core/effect-ir/audit.ts
|
|
7322
7428
|
init_fingerprint2();
|
|
@@ -7410,8 +7516,8 @@ function evidenceRank(level) {
|
|
|
7410
7516
|
}
|
|
7411
7517
|
|
|
7412
7518
|
// src/core/effect-ir/package-exec.ts
|
|
7413
|
-
import { existsSync as
|
|
7414
|
-
import
|
|
7519
|
+
import { existsSync as existsSync6, realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
|
|
7520
|
+
import path26 from "node:path";
|
|
7415
7521
|
|
|
7416
7522
|
// src/core/network-endpoint.ts
|
|
7417
7523
|
var SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "ssh:", "git:"]);
|
|
@@ -7702,18 +7808,18 @@ function classifyPackageAcquisitionSpec(spec) {
|
|
|
7702
7808
|
return { kind: "registry" };
|
|
7703
7809
|
}
|
|
7704
7810
|
function resolveLocalBin(binName, cwd, repoRoot) {
|
|
7705
|
-
const base =
|
|
7811
|
+
const base = path26.basename(binName);
|
|
7706
7812
|
if (!base || base === "." || base === "..") {
|
|
7707
7813
|
return null;
|
|
7708
7814
|
}
|
|
7709
|
-
let current =
|
|
7710
|
-
const stop =
|
|
7815
|
+
let current = path26.resolve(cwd);
|
|
7816
|
+
const stop = path26.resolve(repoRoot);
|
|
7711
7817
|
if (!pathWithinRoot(stop, current)) {
|
|
7712
7818
|
return null;
|
|
7713
7819
|
}
|
|
7714
7820
|
while (true) {
|
|
7715
|
-
const candidate =
|
|
7716
|
-
if (
|
|
7821
|
+
const candidate = path26.join(current, "node_modules", ".bin", base);
|
|
7822
|
+
if (existsSync6(candidate)) {
|
|
7717
7823
|
try {
|
|
7718
7824
|
const resolvedCandidate = realpathSync3.native(candidate);
|
|
7719
7825
|
if (!pathWithinRoot(stop, resolvedCandidate) || !statSync2(resolvedCandidate).isFile()) {
|
|
@@ -7727,7 +7833,7 @@ function resolveLocalBin(binName, cwd, repoRoot) {
|
|
|
7727
7833
|
if (current === stop) {
|
|
7728
7834
|
break;
|
|
7729
7835
|
}
|
|
7730
|
-
const parent =
|
|
7836
|
+
const parent = path26.dirname(current);
|
|
7731
7837
|
if (parent === current) {
|
|
7732
7838
|
break;
|
|
7733
7839
|
}
|
|
@@ -7922,7 +8028,7 @@ init_path_utils();
|
|
|
7922
8028
|
|
|
7923
8029
|
// src/core/verdict/containment.ts
|
|
7924
8030
|
init_git_resource_identity();
|
|
7925
|
-
import
|
|
8031
|
+
import path27 from "node:path";
|
|
7926
8032
|
init_path_utils();
|
|
7927
8033
|
|
|
7928
8034
|
// src/core/verdict/persistent-paths.ts
|
|
@@ -7942,7 +8048,7 @@ function expandHome(token) {
|
|
|
7942
8048
|
if (!home) {
|
|
7943
8049
|
return token;
|
|
7944
8050
|
}
|
|
7945
|
-
return token === "~" ? home :
|
|
8051
|
+
return token === "~" ? home : path27.join(home, token.slice(2));
|
|
7946
8052
|
}
|
|
7947
8053
|
return token;
|
|
7948
8054
|
}
|
|
@@ -7954,10 +8060,10 @@ function resolveTrustedPath(token, trustedCwd, trusted) {
|
|
|
7954
8060
|
return null;
|
|
7955
8061
|
}
|
|
7956
8062
|
const expanded = expandHome(token);
|
|
7957
|
-
if (
|
|
8063
|
+
if (path27.isAbsolute(expanded)) {
|
|
7958
8064
|
return canonicalPath(expanded);
|
|
7959
8065
|
}
|
|
7960
|
-
return canonicalPath(
|
|
8066
|
+
return canonicalPath(path27.resolve(trustedCwd, expanded));
|
|
7961
8067
|
}
|
|
7962
8068
|
function isGitPath(resolvedPath, repoRoot) {
|
|
7963
8069
|
if (isGitMetadataPath(resolvedPath, repoRoot)) {
|
|
@@ -8074,20 +8180,20 @@ function parseTier1Json(raw) {
|
|
|
8074
8180
|
init_judge_runtime_config();
|
|
8075
8181
|
|
|
8076
8182
|
// src/core/verdict/judge-session-kill-switch.ts
|
|
8077
|
-
import { existsSync as
|
|
8078
|
-
import { mkdir as
|
|
8079
|
-
import
|
|
8183
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
8184
|
+
import { mkdir as mkdir8, readFile as readFile8, unlink as unlink5, writeFile as writeFile4 } from "node:fs/promises";
|
|
8185
|
+
import path28 from "node:path";
|
|
8080
8186
|
var JUDGE_SESSION_KILL_FILE = "judge-session-kill.json";
|
|
8081
8187
|
function judgeSessionKillSwitchPath(stateDir) {
|
|
8082
|
-
return
|
|
8188
|
+
return path28.join(stateDir, JUDGE_SESSION_KILL_FILE);
|
|
8083
8189
|
}
|
|
8084
8190
|
async function readJudgeSessionKillSwitch(stateDir) {
|
|
8085
8191
|
const filePath = judgeSessionKillSwitchPath(stateDir);
|
|
8086
|
-
if (!
|
|
8192
|
+
if (!existsSync7(filePath)) {
|
|
8087
8193
|
return null;
|
|
8088
8194
|
}
|
|
8089
8195
|
try {
|
|
8090
|
-
const raw = JSON.parse(await
|
|
8196
|
+
const raw = JSON.parse(await readFile8(filePath, "utf8"));
|
|
8091
8197
|
return raw.triggered === true ? raw : null;
|
|
8092
8198
|
} catch {
|
|
8093
8199
|
return null;
|
|
@@ -8098,7 +8204,7 @@ async function isJudgeSessionKillSwitchPersisted(stateDir) {
|
|
|
8098
8204
|
return record?.triggered === true;
|
|
8099
8205
|
}
|
|
8100
8206
|
async function persistJudgeSessionKillSwitch(stateDir, reason = "shadow_mismatch") {
|
|
8101
|
-
await
|
|
8207
|
+
await mkdir8(stateDir, { recursive: true, mode: 448 });
|
|
8102
8208
|
const record = {
|
|
8103
8209
|
triggered: true,
|
|
8104
8210
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8259,10 +8365,10 @@ function recordJudgeLatency(phase, latencyMs, at = Date.now()) {
|
|
|
8259
8365
|
|
|
8260
8366
|
// src/core/verdict/judge-broker-service.ts
|
|
8261
8367
|
import { spawn as spawn6 } from "node:child_process";
|
|
8262
|
-
import { existsSync as
|
|
8263
|
-
import { unlink as
|
|
8368
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
8369
|
+
import { unlink as unlink6 } from "node:fs/promises";
|
|
8264
8370
|
import net2 from "node:net";
|
|
8265
|
-
import
|
|
8371
|
+
import path29 from "node:path";
|
|
8266
8372
|
import { fileURLToPath } from "node:url";
|
|
8267
8373
|
|
|
8268
8374
|
// src/core/verdict/judge-cli.ts
|
|
@@ -8808,10 +8914,10 @@ var JUDGE_BROKER_SESSION = "judge-broker-session.json";
|
|
|
8808
8914
|
function judgeBrokerPaths(stateDir) {
|
|
8809
8915
|
return {
|
|
8810
8916
|
stateDir,
|
|
8811
|
-
socketPath:
|
|
8812
|
-
statusPath:
|
|
8813
|
-
pidPath:
|
|
8814
|
-
sessionConfigPath:
|
|
8917
|
+
socketPath: path29.join(stateDir, JUDGE_BROKER_SOCKET),
|
|
8918
|
+
statusPath: path29.join(stateDir, JUDGE_BROKER_STATUS),
|
|
8919
|
+
pidPath: path29.join(stateDir, JUDGE_BROKER_PID),
|
|
8920
|
+
sessionConfigPath: path29.join(stateDir, JUDGE_BROKER_SESSION)
|
|
8815
8921
|
};
|
|
8816
8922
|
}
|
|
8817
8923
|
function daemonScriptPath() {
|
|
@@ -8829,12 +8935,12 @@ function useInProcessBroker(env = process.env) {
|
|
|
8829
8935
|
return Boolean(env.VITEST || env.VITEST_WORKER_ID || env.BELAY_JUDGE_BROKER_IN_PROCESS === "1");
|
|
8830
8936
|
}
|
|
8831
8937
|
async function readBrokerStatus(statusPath) {
|
|
8832
|
-
if (!
|
|
8938
|
+
if (!existsSync8(statusPath)) {
|
|
8833
8939
|
return null;
|
|
8834
8940
|
}
|
|
8835
8941
|
try {
|
|
8836
|
-
const { readFile:
|
|
8837
|
-
const raw = JSON.parse(await
|
|
8942
|
+
const { readFile: readFile18 } = await import("node:fs/promises");
|
|
8943
|
+
const raw = JSON.parse(await readFile18(statusPath, "utf8"));
|
|
8838
8944
|
if (typeof raw.pid !== "number" || typeof raw.socketPath !== "string") {
|
|
8839
8945
|
return null;
|
|
8840
8946
|
}
|
|
@@ -8844,13 +8950,13 @@ async function readBrokerStatus(statusPath) {
|
|
|
8844
8950
|
}
|
|
8845
8951
|
}
|
|
8846
8952
|
async function readBrokerSessionConfig(sessionConfigPath) {
|
|
8847
|
-
if (!
|
|
8953
|
+
if (!existsSync8(sessionConfigPath)) {
|
|
8848
8954
|
return null;
|
|
8849
8955
|
}
|
|
8850
8956
|
try {
|
|
8851
|
-
const { readFile:
|
|
8957
|
+
const { readFile: readFile18 } = await import("node:fs/promises");
|
|
8852
8958
|
const raw = JSON.parse(
|
|
8853
|
-
await
|
|
8959
|
+
await readFile18(sessionConfigPath, "utf8")
|
|
8854
8960
|
);
|
|
8855
8961
|
return normalizeJudgeSessionConfig({ ...raw, enabled: true });
|
|
8856
8962
|
} catch {
|
|
@@ -8941,7 +9047,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
|
|
|
8941
9047
|
const existingConfig = await readBrokerSessionConfig(paths.sessionConfigPath);
|
|
8942
9048
|
const configChanged = existingConfig !== null && brokerSessionConfigPayload(existingConfig) !== nextConfigPayload;
|
|
8943
9049
|
const status = await readBrokerStatus(paths.statusPath);
|
|
8944
|
-
if (status && isProcessAlive2(status.pid) &&
|
|
9050
|
+
if (status && isProcessAlive2(status.pid) && existsSync8(paths.socketPath) && !configChanged) {
|
|
8945
9051
|
await writeBrokerSessionConfig(paths, brokerSessionConfig);
|
|
8946
9052
|
return paths;
|
|
8947
9053
|
}
|
|
@@ -8963,7 +9069,7 @@ async function ensureJudgeBrokerDaemon(stateDir, repoRoot, sessionConfig) {
|
|
|
8963
9069
|
child.unref();
|
|
8964
9070
|
const deadline = Date.now() + sessionConfig.connectTimeoutMs;
|
|
8965
9071
|
while (Date.now() < deadline) {
|
|
8966
|
-
if (
|
|
9072
|
+
if (existsSync8(paths.socketPath)) {
|
|
8967
9073
|
const live = await readBrokerStatus(paths.statusPath);
|
|
8968
9074
|
if (live && isProcessAlive2(live.pid)) {
|
|
8969
9075
|
return paths;
|
|
@@ -8980,8 +9086,8 @@ async function cleanupJudgeBrokerArtifacts(paths) {
|
|
|
8980
9086
|
paths.pidPath,
|
|
8981
9087
|
paths.sessionConfigPath
|
|
8982
9088
|
]) {
|
|
8983
|
-
if (
|
|
8984
|
-
await
|
|
9089
|
+
if (existsSync8(artifact)) {
|
|
9090
|
+
await unlink6(artifact).catch(() => void 0);
|
|
8985
9091
|
}
|
|
8986
9092
|
}
|
|
8987
9093
|
}
|
|
@@ -9477,9 +9583,9 @@ async function evaluateWithJudgeTransport(request, options = {}) {
|
|
|
9477
9583
|
}
|
|
9478
9584
|
|
|
9479
9585
|
// src/core/capability/gate-shadow-ratchet.ts
|
|
9480
|
-
import { existsSync as
|
|
9481
|
-
import { mkdir as
|
|
9482
|
-
import
|
|
9586
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
9587
|
+
import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
|
|
9588
|
+
import path30 from "node:path";
|
|
9483
9589
|
var DEFAULT_STATE = {
|
|
9484
9590
|
version: 1,
|
|
9485
9591
|
policyJudgeComparisons: 0,
|
|
@@ -9488,15 +9594,15 @@ var DEFAULT_STATE = {
|
|
|
9488
9594
|
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
9489
9595
|
};
|
|
9490
9596
|
function ratchetPath(stateDir) {
|
|
9491
|
-
return
|
|
9597
|
+
return path30.join(stateDir, "gate-shadow-ratchet.json");
|
|
9492
9598
|
}
|
|
9493
9599
|
async function loadState(stateDir) {
|
|
9494
9600
|
const filePath = ratchetPath(stateDir);
|
|
9495
|
-
if (!
|
|
9601
|
+
if (!existsSync9(filePath)) {
|
|
9496
9602
|
return { ...DEFAULT_STATE, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9497
9603
|
}
|
|
9498
9604
|
try {
|
|
9499
|
-
const raw = JSON.parse(await
|
|
9605
|
+
const raw = JSON.parse(await readFile9(filePath, "utf8"));
|
|
9500
9606
|
return {
|
|
9501
9607
|
...DEFAULT_STATE,
|
|
9502
9608
|
...raw,
|
|
@@ -9507,7 +9613,7 @@ async function loadState(stateDir) {
|
|
|
9507
9613
|
}
|
|
9508
9614
|
}
|
|
9509
9615
|
async function saveState(stateDir, state) {
|
|
9510
|
-
await
|
|
9616
|
+
await mkdir9(stateDir, { recursive: true, mode: 448 });
|
|
9511
9617
|
await writeFile5(
|
|
9512
9618
|
ratchetPath(stateDir),
|
|
9513
9619
|
`${JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
@@ -9630,7 +9736,7 @@ function scheduleGateShadowAudit(params) {
|
|
|
9630
9736
|
async function runGatePolicyShadowComparison(params) {
|
|
9631
9737
|
const judge = params.config.judge;
|
|
9632
9738
|
const runtime = normalizeJudgeRuntimeConfig(judge.runtime);
|
|
9633
|
-
const stateDir = params.stateDir ?? belayStateDir(params.config,
|
|
9739
|
+
const stateDir = params.stateDir ?? belayStateDir(params.config, path31.join(params.repoRoot, ".belay"));
|
|
9634
9740
|
const transport = await evaluateWithJudgeTransport(
|
|
9635
9741
|
{
|
|
9636
9742
|
prompt: buildTier1Prompt(params.command),
|
|
@@ -9920,15 +10026,15 @@ async function loadClassifierAuthorization(params) {
|
|
|
9920
10026
|
}
|
|
9921
10027
|
|
|
9922
10028
|
// src/core/capability/allowlist.ts
|
|
9923
|
-
import { existsSync as
|
|
10029
|
+
import { existsSync as existsSync10, readFileSync as readFileSync3 } from "node:fs";
|
|
9924
10030
|
init_config();
|
|
9925
10031
|
init_path_utils();
|
|
9926
|
-
import
|
|
10032
|
+
import path32 from "node:path";
|
|
9927
10033
|
function fsScopeAllowlistPath(config, repoLocalStateDir) {
|
|
9928
|
-
return
|
|
10034
|
+
return path32.join(belayStateDir(config, repoLocalStateDir), "fs-scope-allowlist.json");
|
|
9929
10035
|
}
|
|
9930
10036
|
function loadFsScopeAllowlistSync(filePath) {
|
|
9931
|
-
if (!
|
|
10037
|
+
if (!existsSync10(filePath)) {
|
|
9932
10038
|
return { version: 1, paths: [] };
|
|
9933
10039
|
}
|
|
9934
10040
|
const raw = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
@@ -10020,7 +10126,7 @@ function checkGatedActionLimits(action) {
|
|
|
10020
10126
|
// src/core/capability/paths.ts
|
|
10021
10127
|
init_path_utils();
|
|
10022
10128
|
init_shell_tokenizer();
|
|
10023
|
-
import
|
|
10129
|
+
import path33 from "node:path";
|
|
10024
10130
|
function applyPatchTargets(patch) {
|
|
10025
10131
|
const targets = [];
|
|
10026
10132
|
for (const line of patch.split("\n")) {
|
|
@@ -10085,7 +10191,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
|
|
|
10085
10191
|
const paths = /* @__PURE__ */ new Set();
|
|
10086
10192
|
const filePath = extractToolFilePath(payload);
|
|
10087
10193
|
if (filePath) {
|
|
10088
|
-
const resolved =
|
|
10194
|
+
const resolved = path33.isAbsolute(filePath) ? filePath : path33.resolve(cwd, filePath);
|
|
10089
10195
|
if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
|
|
10090
10196
|
paths.add(resolved);
|
|
10091
10197
|
}
|
|
@@ -10095,7 +10201,7 @@ function collectOutsideRepoPathsFromToolPayload(payload, cwd, repoRoot, trustedW
|
|
|
10095
10201
|
const patch = extractToolPatch(payload);
|
|
10096
10202
|
if (patch) {
|
|
10097
10203
|
for (const target of applyPatchTargets(patch)) {
|
|
10098
|
-
const resolved =
|
|
10204
|
+
const resolved = path33.isAbsolute(target) ? target : path33.resolve(cwd, target);
|
|
10099
10205
|
if (resolveWorkspaceRootMatch(repoRoot, trustedWorkspaceRoots, resolved) === null) {
|
|
10100
10206
|
paths.add(resolved);
|
|
10101
10207
|
}
|
|
@@ -10149,7 +10255,7 @@ function policyReasonToLegacyReason(decision) {
|
|
|
10149
10255
|
// src/core/capability/policy-engine.ts
|
|
10150
10256
|
init_git_resource_identity();
|
|
10151
10257
|
import { createHash as createHash10 } from "node:crypto";
|
|
10152
|
-
import
|
|
10258
|
+
import path34 from "node:path";
|
|
10153
10259
|
init_path_utils();
|
|
10154
10260
|
init_shell_tokenizer();
|
|
10155
10261
|
init_grant();
|
|
@@ -10441,7 +10547,7 @@ function hashSession(value) {
|
|
|
10441
10547
|
return createHash10("sha256").update(value).digest("hex").slice(0, 16);
|
|
10442
10548
|
}
|
|
10443
10549
|
function resolveCapabilityPath2(targetPath, cwd) {
|
|
10444
|
-
const joined =
|
|
10550
|
+
const joined = path34.isAbsolute(targetPath) ? targetPath : path34.join(cwd, targetPath);
|
|
10445
10551
|
return canonicalPath(joined);
|
|
10446
10552
|
}
|
|
10447
10553
|
function actionForFileMutation(analysis) {
|
|
@@ -10520,7 +10626,7 @@ function isRepoLocalPackageExec(request) {
|
|
|
10520
10626
|
return false;
|
|
10521
10627
|
}
|
|
10522
10628
|
const commandPath = canonicalPath(request.resource.command);
|
|
10523
|
-
return
|
|
10629
|
+
return path34.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
|
|
10524
10630
|
}
|
|
10525
10631
|
function isRepoLocalRoutineWrite(request, sensitivePaths) {
|
|
10526
10632
|
if (!isRepoLocalShellLocation(request)) {
|
|
@@ -10768,6 +10874,40 @@ function evaluateFileMutationPolicy(analysis, config, auth) {
|
|
|
10768
10874
|
);
|
|
10769
10875
|
return { request, decision };
|
|
10770
10876
|
}
|
|
10877
|
+
function buildFileReadCapabilityRequest(analysis) {
|
|
10878
|
+
return {
|
|
10879
|
+
version: CAPABILITY_REQUEST_VERSION,
|
|
10880
|
+
principal: {
|
|
10881
|
+
adapter: analysis.adapter,
|
|
10882
|
+
repoRoot: analysis.repoRoot,
|
|
10883
|
+
sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`)
|
|
10884
|
+
},
|
|
10885
|
+
action: "fs.read",
|
|
10886
|
+
resource: { kind: "path", path: analysis.resolvedPath },
|
|
10887
|
+
context: {
|
|
10888
|
+
cwd: analysis.cwd,
|
|
10889
|
+
inputFingerprint: analysis.inputFingerprint,
|
|
10890
|
+
hookKind: analysis.hookKind,
|
|
10891
|
+
analysisBasis: [`tool:${analysis.toolKind}`, `location:${analysis.locationLabel}`, "read"]
|
|
10892
|
+
},
|
|
10893
|
+
evidence: {
|
|
10894
|
+
level: analysis.locationLabel === "repo_local" ? "certain" : "possible",
|
|
10895
|
+
signals: [...analysis.signals, "effect.fs_read"]
|
|
10896
|
+
}
|
|
10897
|
+
};
|
|
10898
|
+
}
|
|
10899
|
+
function evaluateFileReadPolicy(analysis, config, auth) {
|
|
10900
|
+
const request = buildFileReadCapabilityRequest(analysis);
|
|
10901
|
+
const enriched = enrichAuthWithMaterializedGrants(request, config, {
|
|
10902
|
+
...auth,
|
|
10903
|
+
sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths
|
|
10904
|
+
});
|
|
10905
|
+
const decision = getDefaultPolicyEngine().evaluate(
|
|
10906
|
+
request,
|
|
10907
|
+
buildAuthorizationContext(config, analysis.trustedWorkspaceRoots, enriched)
|
|
10908
|
+
);
|
|
10909
|
+
return { request, decision };
|
|
10910
|
+
}
|
|
10771
10911
|
|
|
10772
10912
|
// src/core/capability/reasons.ts
|
|
10773
10913
|
var FS_SCOPE_REASONS = /* @__PURE__ */ new Set(["outside_repo_mutation", "outside_repo_redirect"]);
|
|
@@ -10784,15 +10924,15 @@ function shouldSkipBrokerApprovedRecord(brokerActive, approvalReason) {
|
|
|
10784
10924
|
}
|
|
10785
10925
|
|
|
10786
10926
|
// src/core/capability/trusted-workspace-roots.ts
|
|
10787
|
-
import { existsSync as
|
|
10927
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
10788
10928
|
init_config();
|
|
10789
10929
|
init_path_utils();
|
|
10790
|
-
import
|
|
10930
|
+
import path35 from "node:path";
|
|
10791
10931
|
function trustedWorkspaceRootsPath(config, repoLocalStateDir) {
|
|
10792
|
-
return
|
|
10932
|
+
return path35.join(belayStateDir(config, repoLocalStateDir), "trusted-workspace-roots.json");
|
|
10793
10933
|
}
|
|
10794
10934
|
function loadTrustedWorkspaceRootsSync(filePath) {
|
|
10795
|
-
if (!
|
|
10935
|
+
if (!existsSync11(filePath)) {
|
|
10796
10936
|
return { version: 1, roots: [] };
|
|
10797
10937
|
}
|
|
10798
10938
|
const raw = JSON.parse(readFileSync4(filePath, "utf8"));
|
|
@@ -10818,7 +10958,7 @@ function sanitizeTrustedWorkspaceRootEntries(input) {
|
|
|
10818
10958
|
const source = record.source === "approval" ? "approval" : void 0;
|
|
10819
10959
|
return [
|
|
10820
10960
|
{
|
|
10821
|
-
path:
|
|
10961
|
+
path: path35.resolve(record.path),
|
|
10822
10962
|
approvedAt,
|
|
10823
10963
|
approvalId,
|
|
10824
10964
|
...source ? { source } : {}
|
|
@@ -10847,7 +10987,7 @@ function isDirectoryPath(targetPath) {
|
|
|
10847
10987
|
function isBroadTrustedWorkspaceRoot(targetPath) {
|
|
10848
10988
|
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
10849
10989
|
const root = normalizeTrustedWorkspaceRootPath(targetPath);
|
|
10850
|
-
if (root === normalizeTrustedWorkspaceRootPath(
|
|
10990
|
+
if (root === normalizeTrustedWorkspaceRootPath(path35.parse(root).root)) {
|
|
10851
10991
|
return true;
|
|
10852
10992
|
}
|
|
10853
10993
|
if (home && normalizeTrustedWorkspaceRootPath(home) === root) {
|
|
@@ -10871,7 +11011,7 @@ function isHighStakesTrustedWorkspaceRoot(targetPath) {
|
|
|
10871
11011
|
return false;
|
|
10872
11012
|
}
|
|
10873
11013
|
return HOME_HIGH_STAKES_SEGMENTS.some(
|
|
10874
|
-
(segment) => pathWithinRoot(
|
|
11014
|
+
(segment) => pathWithinRoot(path35.join(homeRoot, segment), normalized)
|
|
10875
11015
|
);
|
|
10876
11016
|
}
|
|
10877
11017
|
function validateTrustedWorkspaceRootCandidate(params) {
|
|
@@ -10908,7 +11048,7 @@ function validateTrustedWorkspaceRootCandidate(params) {
|
|
|
10908
11048
|
init_config_layers();
|
|
10909
11049
|
|
|
10910
11050
|
// src/core/contained-execution/eligibility.ts
|
|
10911
|
-
import
|
|
11051
|
+
import path36 from "node:path";
|
|
10912
11052
|
init_path_utils();
|
|
10913
11053
|
var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
|
|
10914
11054
|
"command_substitution",
|
|
@@ -10925,7 +11065,7 @@ var FORBIDDEN_SIGNALS = /* @__PURE__ */ new Set([
|
|
|
10925
11065
|
]);
|
|
10926
11066
|
function isContainedUnknownExecutionEligible(config, action, result) {
|
|
10927
11067
|
const contained = config.sandbox.containedExecution;
|
|
10928
|
-
if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !
|
|
11068
|
+
if (!contained?.enabled || !config.sandbox.enabled || config.sandbox.runtime !== "container" || action.kind !== "shell" || !path36.isAbsolute(action.repoRoot) || !hasResolvedRepoIdentity(action.repoRoot) || !config.gates.shell || result.reason !== "unknown_local_effect" || result.axes?.location !== "repo_local" || result.assessment.external) {
|
|
10929
11069
|
return false;
|
|
10930
11070
|
}
|
|
10931
11071
|
const plan = result.effectPlan;
|
|
@@ -11205,7 +11345,7 @@ function classifySubagent(payload, repoRoot, options = {}, config) {
|
|
|
11205
11345
|
}
|
|
11206
11346
|
|
|
11207
11347
|
// src/core/classify-tool.ts
|
|
11208
|
-
import
|
|
11348
|
+
import path51 from "node:path";
|
|
11209
11349
|
init_fingerprint2();
|
|
11210
11350
|
init_path_utils();
|
|
11211
11351
|
init_scrub();
|
|
@@ -11325,13 +11465,13 @@ function worstEffectDecision(decisions) {
|
|
|
11325
11465
|
|
|
11326
11466
|
// src/core/effect-ir/shell-lower.ts
|
|
11327
11467
|
init_shell_tokenizer();
|
|
11328
|
-
import
|
|
11468
|
+
import path50 from "node:path";
|
|
11329
11469
|
|
|
11330
11470
|
// src/core/verdict/docker-compose-run.ts
|
|
11331
|
-
import
|
|
11471
|
+
import path38 from "node:path";
|
|
11332
11472
|
|
|
11333
11473
|
// src/core/verdict/recursive-invocation.ts
|
|
11334
|
-
import
|
|
11474
|
+
import path37 from "node:path";
|
|
11335
11475
|
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
|
|
11336
11476
|
var PYTHON_INTERPRETERS = /* @__PURE__ */ new Set(["python", "python3"]);
|
|
11337
11477
|
var SHELL_SHORT_OPTIONS = /* @__PURE__ */ new Set(["c", "l", "e", "x", "u"]);
|
|
@@ -11393,7 +11533,7 @@ var OSASCRIPT_PROFILE = {
|
|
|
11393
11533
|
attachedValuePrefixes: []
|
|
11394
11534
|
};
|
|
11395
11535
|
function normalizeInterpreter(value) {
|
|
11396
|
-
return
|
|
11536
|
+
return path37.basename(value);
|
|
11397
11537
|
}
|
|
11398
11538
|
function scriptResult(interpreter, token) {
|
|
11399
11539
|
if (!token) {
|
|
@@ -11623,7 +11763,7 @@ function parseOptions(words, start, options) {
|
|
|
11623
11763
|
function decodeDockerComposeRun(tokens) {
|
|
11624
11764
|
if (tokens.some((token) => token.kind === "operator")) return { kind: "none" };
|
|
11625
11765
|
const words = tokens.filter((token) => token.kind === "word");
|
|
11626
|
-
const head =
|
|
11766
|
+
const head = path38.basename(words[0]?.value ?? "");
|
|
11627
11767
|
let index;
|
|
11628
11768
|
if (head === "docker-compose") {
|
|
11629
11769
|
index = 1;
|
|
@@ -11664,7 +11804,7 @@ function decodeDockerComposeRun(tokens) {
|
|
|
11664
11804
|
}
|
|
11665
11805
|
|
|
11666
11806
|
// src/core/verdict/egress-classify.ts
|
|
11667
|
-
import
|
|
11807
|
+
import path39 from "node:path";
|
|
11668
11808
|
var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
|
|
11669
11809
|
"aws",
|
|
11670
11810
|
"curl",
|
|
@@ -11713,7 +11853,7 @@ function isEgressToolHead(head) {
|
|
|
11713
11853
|
return EGRESS_TOOL_HEADS.has(head);
|
|
11714
11854
|
}
|
|
11715
11855
|
function decodeEgressEffects(params) {
|
|
11716
|
-
const head =
|
|
11856
|
+
const head = path39.basename(params.tokens[0] ?? "");
|
|
11717
11857
|
if (head !== "curl" && head !== "wget" && head !== "gh") {
|
|
11718
11858
|
return null;
|
|
11719
11859
|
}
|
|
@@ -11721,7 +11861,7 @@ function decodeEgressEffects(params) {
|
|
|
11721
11861
|
const provenance = { segment: params.segment };
|
|
11722
11862
|
const requirements = [];
|
|
11723
11863
|
for (const file of decoded.files) {
|
|
11724
|
-
const resolved =
|
|
11864
|
+
const resolved = path39.resolve(params.cwd, expandHome2(file));
|
|
11725
11865
|
requirements.push(
|
|
11726
11866
|
requirement("fs.read", "fs.read", { kind: "path", path: resolved }, params.segment, [
|
|
11727
11867
|
"egress.explicit_file_read"
|
|
@@ -11743,7 +11883,7 @@ function decodeEgressEffects(params) {
|
|
|
11743
11883
|
if (file === "-") {
|
|
11744
11884
|
continue;
|
|
11745
11885
|
}
|
|
11746
|
-
const resolved =
|
|
11886
|
+
const resolved = path39.resolve(params.cwd, expandHome2(file));
|
|
11747
11887
|
if (resolved === "/dev/null") {
|
|
11748
11888
|
continue;
|
|
11749
11889
|
}
|
|
@@ -12221,13 +12361,13 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
|
|
|
12221
12361
|
if (head === "wget" && !explicitOutput) {
|
|
12222
12362
|
outputFiles.push(
|
|
12223
12363
|
...endpointOutputNames.map(
|
|
12224
|
-
(name) => outputDirectory ?
|
|
12364
|
+
(name) => outputDirectory ? path39.join(outputDirectory, name) : name
|
|
12225
12365
|
)
|
|
12226
12366
|
);
|
|
12227
12367
|
} else if (head === "curl" && remoteNameOutput) {
|
|
12228
12368
|
outputFiles.push(
|
|
12229
12369
|
...endpointOutputNames.map(
|
|
12230
|
-
(name) => outputDirectory ?
|
|
12370
|
+
(name) => outputDirectory ? path39.join(outputDirectory, name) : name
|
|
12231
12371
|
)
|
|
12232
12372
|
);
|
|
12233
12373
|
}
|
|
@@ -12241,7 +12381,7 @@ function decodeSingleCurlWgetGrammar(head, tokens) {
|
|
|
12241
12381
|
outputFiles: [
|
|
12242
12382
|
...new Set(
|
|
12243
12383
|
outputFiles.map(
|
|
12244
|
-
(file) => outputDirectory && directoryEligibleOutputs.has(file) && !
|
|
12384
|
+
(file) => outputDirectory && directoryEligibleOutputs.has(file) && !path39.isAbsolute(file) ? path39.join(outputDirectory, file) : file
|
|
12245
12385
|
)
|
|
12246
12386
|
)
|
|
12247
12387
|
],
|
|
@@ -12256,7 +12396,7 @@ function remoteOutputName(spec) {
|
|
|
12256
12396
|
} catch {
|
|
12257
12397
|
pathname = spec.split(/[?#]/, 1)[0] ?? "";
|
|
12258
12398
|
}
|
|
12259
|
-
const name =
|
|
12399
|
+
const name = path39.posix.basename(pathname);
|
|
12260
12400
|
return name && name !== "/" ? name : "index.html";
|
|
12261
12401
|
}
|
|
12262
12402
|
function decodeGhGrammar(tokens) {
|
|
@@ -12421,7 +12561,7 @@ function expandHome2(value) {
|
|
|
12421
12561
|
return process.env.HOME ?? value;
|
|
12422
12562
|
}
|
|
12423
12563
|
if (value.startsWith("~/")) {
|
|
12424
|
-
return
|
|
12564
|
+
return path39.join(process.env.HOME ?? "~", value.slice(2));
|
|
12425
12565
|
}
|
|
12426
12566
|
return value;
|
|
12427
12567
|
}
|
|
@@ -12440,7 +12580,7 @@ function requirement(tag, action, resource, segment, signals) {
|
|
|
12440
12580
|
}
|
|
12441
12581
|
|
|
12442
12582
|
// src/core/verdict/git-classifier.ts
|
|
12443
|
-
import
|
|
12583
|
+
import path40 from "node:path";
|
|
12444
12584
|
init_shell_tokenizer();
|
|
12445
12585
|
var GIT_BRANCH_MUTATION_FLAGS = /* @__PURE__ */ new Set([
|
|
12446
12586
|
"--copy",
|
|
@@ -12536,7 +12676,7 @@ var FILE_OPERAND_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
12536
12676
|
var COMPOUND_SUBCOMMAND_HEADS = /* @__PURE__ */ new Set(["worktree", "stash", "tag"]);
|
|
12537
12677
|
var REF_ONLY_WITHOUT_TERMINATOR = /* @__PURE__ */ new Set(["checkout", "show", "log"]);
|
|
12538
12678
|
function isGitExecutable(token) {
|
|
12539
|
-
return
|
|
12679
|
+
return path40.basename(token) === "git";
|
|
12540
12680
|
}
|
|
12541
12681
|
function takesValue(flag) {
|
|
12542
12682
|
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=");
|
|
@@ -12564,7 +12704,7 @@ function peelGlobalOptions(tokens, baseCwd) {
|
|
|
12564
12704
|
if (token === "-C" || token === "--work-tree" || token === "--git-dir" || token === "-c") {
|
|
12565
12705
|
const value = tokens[index + 1];
|
|
12566
12706
|
if (token === "-C" && value) {
|
|
12567
|
-
effectiveCwd =
|
|
12707
|
+
effectiveCwd = path40.resolve(baseCwd, value);
|
|
12568
12708
|
} else if (token === "--work-tree" && value) {
|
|
12569
12709
|
workTree = value;
|
|
12570
12710
|
} else if (token === "--git-dir" && value) {
|
|
@@ -12574,7 +12714,7 @@ function peelGlobalOptions(tokens, baseCwd) {
|
|
|
12574
12714
|
continue;
|
|
12575
12715
|
}
|
|
12576
12716
|
if (token.startsWith("-C") && token.length > 2) {
|
|
12577
|
-
effectiveCwd =
|
|
12717
|
+
effectiveCwd = path40.resolve(baseCwd, token.slice(2));
|
|
12578
12718
|
index += 1;
|
|
12579
12719
|
continue;
|
|
12580
12720
|
}
|
|
@@ -12717,7 +12857,7 @@ function looksLikeDiffPathOperand(token) {
|
|
|
12717
12857
|
if (!looksLikeFileOperand(token)) {
|
|
12718
12858
|
return false;
|
|
12719
12859
|
}
|
|
12720
|
-
if (token.startsWith(".") ||
|
|
12860
|
+
if (token.startsWith(".") || path40.isAbsolute(token)) {
|
|
12721
12861
|
return true;
|
|
12722
12862
|
}
|
|
12723
12863
|
return token.includes(".");
|
|
@@ -12725,12 +12865,12 @@ function looksLikeDiffPathOperand(token) {
|
|
|
12725
12865
|
function resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir) {
|
|
12726
12866
|
const resolveBase = effectiveCwd ?? baseCwd;
|
|
12727
12867
|
if (workTree) {
|
|
12728
|
-
return
|
|
12868
|
+
return path40.resolve(resolveBase, workTree);
|
|
12729
12869
|
}
|
|
12730
12870
|
if (gitDir) {
|
|
12731
|
-
const resolvedGitDir =
|
|
12732
|
-
if (
|
|
12733
|
-
return
|
|
12871
|
+
const resolvedGitDir = path40.resolve(resolveBase, gitDir);
|
|
12872
|
+
if (path40.basename(resolvedGitDir) === ".git") {
|
|
12873
|
+
return path40.dirname(resolvedGitDir);
|
|
12734
12874
|
}
|
|
12735
12875
|
}
|
|
12736
12876
|
return void 0;
|
|
@@ -12811,7 +12951,7 @@ function classifyGitCommand(tokens, baseCwd) {
|
|
|
12811
12951
|
const { subcommand, args, effectiveCwd, gitDir, workTree } = normalized;
|
|
12812
12952
|
const normalizedKey = `git ${subcommand}`;
|
|
12813
12953
|
const gitWorkTree = resolveGitWorkTree(baseCwd, effectiveCwd, workTree, gitDir);
|
|
12814
|
-
const effectiveGitDir = gitDir ?
|
|
12954
|
+
const effectiveGitDir = gitDir ? path40.resolve(effectiveCwd ?? baseCwd, gitDir) : void 0;
|
|
12815
12955
|
const scopeTargets = [effectiveCwd, gitWorkTree, effectiveGitDir].filter(
|
|
12816
12956
|
(target, index, targets) => Boolean(target) && targets.indexOf(target) === index
|
|
12817
12957
|
);
|
|
@@ -12963,9 +13103,9 @@ function decodeGitEffects(params) {
|
|
|
12963
13103
|
...subcommand === "push" ? ["tier0_external"] : []
|
|
12964
13104
|
];
|
|
12965
13105
|
const effectiveCwd = normalized.effectiveCwd ?? params.cwd;
|
|
12966
|
-
const workTreeRoot = normalized.workTree ?
|
|
12967
|
-
const gitRefRoot = normalized.gitDir ?
|
|
12968
|
-
const gitControlRoot = normalized.gitDir ? gitRefRoot :
|
|
13106
|
+
const workTreeRoot = normalized.workTree ? path40.resolve(normalized.effectiveCwd ?? params.cwd, normalized.workTree) : normalized.effectiveCwd ?? params.repoRoot;
|
|
13107
|
+
const gitRefRoot = normalized.gitDir ? path40.resolve(effectiveCwd, normalized.gitDir) : workTreeRoot;
|
|
13108
|
+
const gitControlRoot = normalized.gitDir ? gitRefRoot : path40.join(gitRefRoot, ".git");
|
|
12969
13109
|
const requirements = [];
|
|
12970
13110
|
if (subcommand === "fetch" || subcommand === "pull") {
|
|
12971
13111
|
const positionals = gitRemotePositionals(args);
|
|
@@ -13124,7 +13264,7 @@ function decodeGitEffects(params) {
|
|
|
13124
13264
|
gitRequirement(
|
|
13125
13265
|
"control_plane.write",
|
|
13126
13266
|
"control_plane.write",
|
|
13127
|
-
{ kind: "path", path:
|
|
13267
|
+
{ kind: "path", path: path40.join(gitControlRoot, "logs") },
|
|
13128
13268
|
params.segment,
|
|
13129
13269
|
[...signals, "git_history_destructive", "git.reflog.mutate"]
|
|
13130
13270
|
)
|
|
@@ -13182,7 +13322,7 @@ function decodeGitEffects(params) {
|
|
|
13182
13322
|
gitRequirement(
|
|
13183
13323
|
"fs.read",
|
|
13184
13324
|
"fs.read",
|
|
13185
|
-
{ kind: "path", path:
|
|
13325
|
+
{ kind: "path", path: path40.resolve(workTreeRoot, operand) },
|
|
13186
13326
|
params.segment,
|
|
13187
13327
|
[...signals, "git.path.read"]
|
|
13188
13328
|
)
|
|
@@ -13217,7 +13357,7 @@ function decodeGitEffects(params) {
|
|
|
13217
13357
|
gitRequirement(
|
|
13218
13358
|
"fs.write",
|
|
13219
13359
|
"fs.write",
|
|
13220
|
-
{ kind: "path", path:
|
|
13360
|
+
{ kind: "path", path: path40.resolve(workTreeRoot, operand) },
|
|
13221
13361
|
params.segment,
|
|
13222
13362
|
[...signals, "git.path.write"]
|
|
13223
13363
|
)
|
|
@@ -13401,8 +13541,8 @@ function gitRequirement(tag, action, resource, segment, signals) {
|
|
|
13401
13541
|
}
|
|
13402
13542
|
|
|
13403
13543
|
// src/core/verdict/launcher-resolve.ts
|
|
13404
|
-
import { existsSync as
|
|
13405
|
-
import
|
|
13544
|
+
import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
|
|
13545
|
+
import path41 from "node:path";
|
|
13406
13546
|
|
|
13407
13547
|
// src/core/verdict/makefile-expand.ts
|
|
13408
13548
|
var MAX_EXPAND_DEPTH = 16;
|
|
@@ -13575,8 +13715,8 @@ var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
13575
13715
|
"why"
|
|
13576
13716
|
]);
|
|
13577
13717
|
function readPackageJson(dir) {
|
|
13578
|
-
const packagePath =
|
|
13579
|
-
if (!
|
|
13718
|
+
const packagePath = path41.join(dir, "package.json");
|
|
13719
|
+
if (!existsSync12(packagePath)) {
|
|
13580
13720
|
return null;
|
|
13581
13721
|
}
|
|
13582
13722
|
try {
|
|
@@ -13586,17 +13726,17 @@ function readPackageJson(dir) {
|
|
|
13586
13726
|
}
|
|
13587
13727
|
}
|
|
13588
13728
|
function findPackageJson(startDir, stopDir) {
|
|
13589
|
-
let current =
|
|
13590
|
-
const stop =
|
|
13729
|
+
let current = path41.resolve(startDir);
|
|
13730
|
+
const stop = path41.resolve(stopDir);
|
|
13591
13731
|
while (true) {
|
|
13592
|
-
const packagePath =
|
|
13593
|
-
if (
|
|
13732
|
+
const packagePath = path41.join(current, "package.json");
|
|
13733
|
+
if (existsSync12(packagePath)) {
|
|
13594
13734
|
return packagePath;
|
|
13595
13735
|
}
|
|
13596
|
-
if (current === stop || current ===
|
|
13597
|
-
return
|
|
13736
|
+
if (current === stop || current === path41.dirname(current)) {
|
|
13737
|
+
return existsSync12(packagePath) ? packagePath : null;
|
|
13598
13738
|
}
|
|
13599
|
-
const parent =
|
|
13739
|
+
const parent = path41.dirname(current);
|
|
13600
13740
|
if (!parent.startsWith(stop) && parent !== current) {
|
|
13601
13741
|
}
|
|
13602
13742
|
if (parent === current) {
|
|
@@ -13653,7 +13793,7 @@ function resolveNpmRecipe(cwd, repoRoot, scriptName, extraArgs) {
|
|
|
13653
13793
|
}
|
|
13654
13794
|
return { recipes: [], opaque: true, reason: "package_json_missing" };
|
|
13655
13795
|
}
|
|
13656
|
-
const pkg = readPackageJson(
|
|
13796
|
+
const pkg = readPackageJson(path41.dirname(packagePath));
|
|
13657
13797
|
const scripts = pkg?.scripts;
|
|
13658
13798
|
if (!scripts || typeof scripts !== "object") {
|
|
13659
13799
|
return { recipes: [], opaque: true, reason: "package_scripts_missing" };
|
|
@@ -13744,20 +13884,20 @@ function parseMakefileRecipeContent(content) {
|
|
|
13744
13884
|
function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
13745
13885
|
const candidates = ["Makefile", "makefile", "GNUmakefile"];
|
|
13746
13886
|
let makefilePath = null;
|
|
13747
|
-
let searchDir =
|
|
13748
|
-
const stop =
|
|
13887
|
+
let searchDir = path41.resolve(cwd);
|
|
13888
|
+
const stop = path41.resolve(repoRoot);
|
|
13749
13889
|
while (true) {
|
|
13750
13890
|
for (const name of candidates) {
|
|
13751
|
-
const candidate =
|
|
13752
|
-
if (
|
|
13891
|
+
const candidate = path41.join(searchDir, name);
|
|
13892
|
+
if (existsSync12(candidate)) {
|
|
13753
13893
|
makefilePath = candidate;
|
|
13754
13894
|
break;
|
|
13755
13895
|
}
|
|
13756
13896
|
}
|
|
13757
|
-
if (makefilePath || searchDir === stop || searchDir ===
|
|
13897
|
+
if (makefilePath || searchDir === stop || searchDir === path41.dirname(searchDir)) {
|
|
13758
13898
|
break;
|
|
13759
13899
|
}
|
|
13760
|
-
searchDir =
|
|
13900
|
+
searchDir = path41.dirname(searchDir);
|
|
13761
13901
|
}
|
|
13762
13902
|
if (!makefilePath) {
|
|
13763
13903
|
return { recipes: [], opaque: true, reason: "unknown_local_effect" };
|
|
@@ -13784,7 +13924,7 @@ function resolveMakeRecipe(cwd, repoRoot, target, cliVars = {}) {
|
|
|
13784
13924
|
}
|
|
13785
13925
|
const entry = targets.get(name);
|
|
13786
13926
|
if (!entry) {
|
|
13787
|
-
if (!
|
|
13927
|
+
if (!existsSync12(path41.resolve(path41.dirname(makefilePath), name))) {
|
|
13788
13928
|
hasUndefinedPrerequisite = true;
|
|
13789
13929
|
}
|
|
13790
13930
|
return;
|
|
@@ -13883,7 +14023,7 @@ function resolveLauncherRecipe(params) {
|
|
|
13883
14023
|
}
|
|
13884
14024
|
|
|
13885
14025
|
// src/core/verdict/parser.ts
|
|
13886
|
-
import
|
|
14026
|
+
import path42 from "node:path";
|
|
13887
14027
|
|
|
13888
14028
|
// src/core/shell-substitution.ts
|
|
13889
14029
|
function findStructuralCommandSubstitutions(command) {
|
|
@@ -14123,7 +14263,7 @@ var INTERPRETER_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
14123
14263
|
".sh"
|
|
14124
14264
|
]);
|
|
14125
14265
|
function normalizeHead(token) {
|
|
14126
|
-
const base =
|
|
14266
|
+
const base = path42.basename(token);
|
|
14127
14267
|
if (base && base !== "." && base !== "..") {
|
|
14128
14268
|
return base;
|
|
14129
14269
|
}
|
|
@@ -14433,7 +14573,7 @@ function isBareInterpreter(tokens) {
|
|
|
14433
14573
|
return false;
|
|
14434
14574
|
}
|
|
14435
14575
|
const scriptArg = args.find((token) => !token.startsWith("-"));
|
|
14436
|
-
if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(
|
|
14576
|
+
if (scriptArg && INTERPRETER_SCRIPT_EXTENSIONS.has(path42.extname(scriptArg))) {
|
|
14437
14577
|
return false;
|
|
14438
14578
|
}
|
|
14439
14579
|
if (scriptArg) {
|
|
@@ -14673,7 +14813,7 @@ function mergeNodes(nodes) {
|
|
|
14673
14813
|
}
|
|
14674
14814
|
|
|
14675
14815
|
// src/core/effect-ir/shell-lower/argv-delegate-gate.ts
|
|
14676
|
-
import
|
|
14816
|
+
import path43 from "node:path";
|
|
14677
14817
|
var ARGV_DELEGATE_INNER_BLOCKLIST = /* @__PURE__ */ new Set([
|
|
14678
14818
|
"sudo",
|
|
14679
14819
|
"env",
|
|
@@ -14694,7 +14834,7 @@ function shouldApplyArgvDelegate(head, innerTokens, depth) {
|
|
|
14694
14834
|
if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
|
|
14695
14835
|
return false;
|
|
14696
14836
|
}
|
|
14697
|
-
const innerHead =
|
|
14837
|
+
const innerHead = path43.basename(innerTokens[0] ?? "");
|
|
14698
14838
|
if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
|
|
14699
14839
|
return false;
|
|
14700
14840
|
}
|
|
@@ -14801,7 +14941,7 @@ function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegm
|
|
|
14801
14941
|
|
|
14802
14942
|
// src/core/effect-ir/shell-lower/tokens.ts
|
|
14803
14943
|
init_shell_tokenizer();
|
|
14804
|
-
import
|
|
14944
|
+
import path44 from "node:path";
|
|
14805
14945
|
var ENV_PREFIX_PATTERN2 = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
14806
14946
|
var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0:0:0:0:0:0:0:1"]);
|
|
14807
14947
|
var METADATA_ONLY_FLAGS = /* @__PURE__ */ new Set(["--version", "-v", "-V", "--help", "-h"]);
|
|
@@ -14945,9 +15085,9 @@ function resolvePathOperand(operand, cwd) {
|
|
|
14945
15085
|
return process.env.HOME ?? operand;
|
|
14946
15086
|
}
|
|
14947
15087
|
if (operand.startsWith("~/")) {
|
|
14948
|
-
return
|
|
15088
|
+
return path44.join(process.env.HOME ?? "~", operand.slice(2));
|
|
14949
15089
|
}
|
|
14950
|
-
return
|
|
15090
|
+
return path44.resolve(cwd, operand);
|
|
14951
15091
|
}
|
|
14952
15092
|
function isOptionToken(value) {
|
|
14953
15093
|
return value.startsWith("-") || value.startsWith("+");
|
|
@@ -14959,7 +15099,7 @@ function pipeToShell(command) {
|
|
|
14959
15099
|
return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
|
|
14960
15100
|
}
|
|
14961
15101
|
function executableBaseName(head) {
|
|
14962
|
-
return
|
|
15102
|
+
return path44.basename(head);
|
|
14963
15103
|
}
|
|
14964
15104
|
function isMetadataOnlyArgv(argv) {
|
|
14965
15105
|
return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
|
|
@@ -15078,15 +15218,22 @@ function effectChangingEnvironmentSignals(head, env, changedNames) {
|
|
|
15078
15218
|
}
|
|
15079
15219
|
|
|
15080
15220
|
// src/core/effect-ir/shell-lower/decode-process.ts
|
|
15081
|
-
import
|
|
15221
|
+
import path48 from "node:path";
|
|
15082
15222
|
|
|
15083
15223
|
// src/core/effect-ir/shell-lower/decoders/belay.ts
|
|
15084
|
-
import
|
|
15224
|
+
import path45 from "node:path";
|
|
15085
15225
|
function decodeBelay(args, repoRoot, segment) {
|
|
15086
15226
|
const [section, operation, key] = args;
|
|
15087
15227
|
const judgeCommand = section === "judge" && operation !== void 0 && ["consent", "list", "status", "test", "use"].includes(operation);
|
|
15088
15228
|
const configRead = section === "config" && (operation === void 0 || operation === "list" || operation === "get" && key?.startsWith("judge."));
|
|
15089
15229
|
const configJudgeMutation = section === "config" && (["set", "unset"].includes(operation ?? "") && key?.startsWith("judge.") || operation === "credential" && key === "mode");
|
|
15230
|
+
const approvalAuthorityCommand = [
|
|
15231
|
+
"approval-token",
|
|
15232
|
+
"approve",
|
|
15233
|
+
"revoke",
|
|
15234
|
+
"standing-allow"
|
|
15235
|
+
].includes(section ?? "");
|
|
15236
|
+
const configTrustMutation = section === "config" && operation === "trust";
|
|
15090
15237
|
if (judgeCommand || configRead || configJudgeMutation) {
|
|
15091
15238
|
return [
|
|
15092
15239
|
processRequirement("belay", "inspect", segment, [
|
|
@@ -15095,12 +15242,23 @@ function decodeBelay(args, repoRoot, segment) {
|
|
|
15095
15242
|
])
|
|
15096
15243
|
];
|
|
15097
15244
|
}
|
|
15245
|
+
if (approvalAuthorityCommand || configTrustMutation) {
|
|
15246
|
+
return [
|
|
15247
|
+
requirement2(
|
|
15248
|
+
"control_plane.write",
|
|
15249
|
+
"control_plane.write",
|
|
15250
|
+
{ kind: "path", path: path45.join(repoRoot, ".belay-control-plane") },
|
|
15251
|
+
segment,
|
|
15252
|
+
[approvalAuthorityCommand ? "belay.approval_authority" : "belay.config_trust"]
|
|
15253
|
+
)
|
|
15254
|
+
];
|
|
15255
|
+
}
|
|
15098
15256
|
if (section === "config" && ["set", "unset", "credential"].includes(operation ?? "")) {
|
|
15099
15257
|
return [
|
|
15100
15258
|
requirement2(
|
|
15101
15259
|
"control_plane.write",
|
|
15102
15260
|
"control_plane.write",
|
|
15103
|
-
{ kind: "path", path:
|
|
15261
|
+
{ kind: "path", path: path45.join(repoRoot, ".belay-control-plane") },
|
|
15104
15262
|
segment,
|
|
15105
15263
|
["belay.config_non_judge_mutation"]
|
|
15106
15264
|
)
|
|
@@ -15341,7 +15499,7 @@ function validDockerInfo(args) {
|
|
|
15341
15499
|
// src/core/effect-ir/shell-lower/decoders/filesystem.ts
|
|
15342
15500
|
init_git_resource_identity();
|
|
15343
15501
|
import { lstatSync as lstatSync2, realpathSync as realpathSync4 } from "node:fs";
|
|
15344
|
-
import
|
|
15502
|
+
import path46 from "node:path";
|
|
15345
15503
|
function decodeCopyMove(head, args, cwd, segment) {
|
|
15346
15504
|
const requirements = [processRequirement(head, "spawn", segment, ["process.filesystem_mutation"])];
|
|
15347
15505
|
const operands = [];
|
|
@@ -15469,11 +15627,11 @@ function decodeRm(args, cwd, repoRoot, segment) {
|
|
|
15469
15627
|
function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
|
|
15470
15628
|
try {
|
|
15471
15629
|
if (finalOperandIsSymlink) {
|
|
15472
|
-
return
|
|
15630
|
+
return path46.join(realpathSync4.native(path46.dirname(targetPath)), path46.basename(targetPath));
|
|
15473
15631
|
}
|
|
15474
15632
|
return realpathSync4.native(targetPath);
|
|
15475
15633
|
} catch {
|
|
15476
|
-
return
|
|
15634
|
+
return path46.resolve(targetPath);
|
|
15477
15635
|
}
|
|
15478
15636
|
}
|
|
15479
15637
|
function isSymbolicLink(targetPath) {
|
|
@@ -15484,8 +15642,8 @@ function isSymbolicLink(targetPath) {
|
|
|
15484
15642
|
}
|
|
15485
15643
|
}
|
|
15486
15644
|
function pathContains(ancestor, candidate) {
|
|
15487
|
-
const relative =
|
|
15488
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
15645
|
+
const relative = path46.relative(path46.resolve(ancestor), path46.resolve(candidate));
|
|
15646
|
+
return relative === "" || !relative.startsWith("..") && !path46.isAbsolute(relative);
|
|
15489
15647
|
}
|
|
15490
15648
|
function filesystemReadOperands(head, args) {
|
|
15491
15649
|
switch (head) {
|
|
@@ -15625,7 +15783,7 @@ function decodePrisma(args, env, repoRoot, segment) {
|
|
|
15625
15783
|
|
|
15626
15784
|
// src/core/effect-ir/shell-lower/decoders/ruby.ts
|
|
15627
15785
|
init_path_utils();
|
|
15628
|
-
import
|
|
15786
|
+
import path47 from "node:path";
|
|
15629
15787
|
var RAILS_READ_ONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["routes", "middleware", "stats", "about", "version"]);
|
|
15630
15788
|
function railsReadOnlySubcommand(args) {
|
|
15631
15789
|
const subcommand = args[0];
|
|
@@ -15635,7 +15793,7 @@ function railsReadOnlySubcommand(args) {
|
|
|
15635
15793
|
return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
|
|
15636
15794
|
}
|
|
15637
15795
|
function isRubyTestScript(scriptPath) {
|
|
15638
|
-
const base =
|
|
15796
|
+
const base = path47.basename(scriptPath);
|
|
15639
15797
|
return base.endsWith("_test.rb") || base.endsWith("_spec.rb");
|
|
15640
15798
|
}
|
|
15641
15799
|
function parseRubyTestInvocation(args) {
|
|
@@ -16049,7 +16207,7 @@ function decodeProcessOrFilesystem(params) {
|
|
|
16049
16207
|
requirement2(
|
|
16050
16208
|
"fs.read",
|
|
16051
16209
|
"fs.read",
|
|
16052
|
-
{ kind: "path", path:
|
|
16210
|
+
{ kind: "path", path: path48.resolve(cwd, syntax) },
|
|
16053
16211
|
segment,
|
|
16054
16212
|
["shell.syntax_source_read"]
|
|
16055
16213
|
)
|
|
@@ -16255,7 +16413,7 @@ function packageExecInnerIsMetadata(peel) {
|
|
|
16255
16413
|
|
|
16256
16414
|
// src/core/effect-ir/shell-lower/segment.ts
|
|
16257
16415
|
init_shell_tokenizer();
|
|
16258
|
-
import
|
|
16416
|
+
import path49 from "node:path";
|
|
16259
16417
|
var DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
|
|
16260
16418
|
var SHELL_GLOB_PATTERN = /[*?[]/;
|
|
16261
16419
|
function requiresKnownCwd(requirementValue) {
|
|
@@ -16270,11 +16428,11 @@ function joinNestedOpacity(outer, nested) {
|
|
|
16270
16428
|
}
|
|
16271
16429
|
function startsLocalPostgresService(command) {
|
|
16272
16430
|
const tokens = tokenizeShell(command);
|
|
16273
|
-
return
|
|
16431
|
+
return path49.basename(tokens[0] ?? "") === "docker" && tokens[1] === "compose" && ["up", "start", "restart"].includes(tokens[2] ?? "") && tokens.includes("postgres");
|
|
16274
16432
|
}
|
|
16275
16433
|
function resolveCdTransition(command, currentCwd) {
|
|
16276
16434
|
const tokens = tokenizeShell(command);
|
|
16277
|
-
if (
|
|
16435
|
+
if (path49.basename(tokens[0] ?? "") !== "cd") {
|
|
16278
16436
|
return null;
|
|
16279
16437
|
}
|
|
16280
16438
|
const target = tokens[1] ?? "~";
|
|
@@ -16437,7 +16595,7 @@ function lowerSegment(command, context) {
|
|
|
16437
16595
|
stripStructuredRedirects(lexed.tokens),
|
|
16438
16596
|
stripRedirects(parsedTokens)
|
|
16439
16597
|
);
|
|
16440
|
-
const head =
|
|
16598
|
+
const head = path50.basename(tokens[0] ?? parsed.head);
|
|
16441
16599
|
let opacity = segmentOpacity(command);
|
|
16442
16600
|
const signals = /* @__PURE__ */ new Set();
|
|
16443
16601
|
const requirements = [];
|
|
@@ -17020,17 +17178,41 @@ function verdictToClassifyResult(result, config, options = {}) {
|
|
|
17020
17178
|
|
|
17021
17179
|
// src/core/classify-tool.ts
|
|
17022
17180
|
var DEFAULT_SENSITIVE_PATHS = [".env", ".env.*", "**/credentials/**"];
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
"
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17181
|
+
function toolInputRecord(payload) {
|
|
17182
|
+
const toolInput = payload.tool_input;
|
|
17183
|
+
if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
|
|
17184
|
+
return null;
|
|
17185
|
+
}
|
|
17186
|
+
return toolInput;
|
|
17187
|
+
}
|
|
17188
|
+
function hasMutationFields(input) {
|
|
17189
|
+
return typeof input.contents === "string" || typeof input.old_string === "string" || typeof input.new_string === "string" || typeof input.newContents === "string";
|
|
17190
|
+
}
|
|
17191
|
+
function inferToolPayloadEffect(payload, toolName) {
|
|
17192
|
+
if (extractShellCommand(payload)) {
|
|
17193
|
+
return "shell";
|
|
17194
|
+
}
|
|
17195
|
+
const input = toolInputRecord(payload);
|
|
17196
|
+
if (!input) {
|
|
17197
|
+
return "indeterminate";
|
|
17198
|
+
}
|
|
17199
|
+
if (hasMutationFields(input)) {
|
|
17200
|
+
return "file_mutation";
|
|
17201
|
+
}
|
|
17202
|
+
if (extractPatch(payload)) {
|
|
17203
|
+
return "apply_patch";
|
|
17204
|
+
}
|
|
17205
|
+
if (typeof input.pattern === "string" || typeof input.glob_pattern === "string") {
|
|
17206
|
+
return "search_read";
|
|
17207
|
+
}
|
|
17208
|
+
if (typeof input.file_path === "string") {
|
|
17209
|
+
return "file_read";
|
|
17210
|
+
}
|
|
17211
|
+
if (typeof input.path === "string" || typeof input.target_file === "string") {
|
|
17212
|
+
return normalizedToolName(toolName) === "delete" ? "file_delete" : "indeterminate";
|
|
17213
|
+
}
|
|
17214
|
+
return "indeterminate";
|
|
17215
|
+
}
|
|
17034
17216
|
function policyAuth(options) {
|
|
17035
17217
|
if (!options.grants && options.attestation === void 0 && options.egressProxyActive === void 0) {
|
|
17036
17218
|
return void 0;
|
|
@@ -17210,56 +17392,365 @@ function classifyFileMutationWithPolicy(params) {
|
|
|
17210
17392
|
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17211
17393
|
};
|
|
17212
17394
|
}
|
|
17213
|
-
|
|
17214
|
-
const
|
|
17215
|
-
const
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
|
|
17234
|
-
|
|
17235
|
-
|
|
17236
|
-
|
|
17237
|
-
|
|
17238
|
-
|
|
17239
|
-
|
|
17240
|
-
|
|
17241
|
-
|
|
17242
|
-
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17395
|
+
function classifyFileReadWithPolicy(params) {
|
|
17396
|
+
const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
|
|
17397
|
+
const { request, decision } = evaluateFileReadPolicy(
|
|
17398
|
+
{
|
|
17399
|
+
hookKind: "tool",
|
|
17400
|
+
toolKind: params.toolKind,
|
|
17401
|
+
filePath: params.filePath,
|
|
17402
|
+
resolvedPath: params.resolvedPath,
|
|
17403
|
+
repoRoot: params.repoRoot,
|
|
17404
|
+
cwd: params.cwd,
|
|
17405
|
+
inputFingerprint: fingerprint,
|
|
17406
|
+
signals: params.signals,
|
|
17407
|
+
locationLabel: params.locationLabel,
|
|
17408
|
+
trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
|
|
17409
|
+
sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths
|
|
17410
|
+
},
|
|
17411
|
+
params.config,
|
|
17412
|
+
policyAuth(params.options)
|
|
17413
|
+
);
|
|
17414
|
+
if (policyDecisionRequiresAsk(decision)) {
|
|
17415
|
+
return {
|
|
17416
|
+
verdict: "deny_pending_approval",
|
|
17417
|
+
reason: policyReasonToLegacyReason(decision),
|
|
17418
|
+
summary: params.filePath,
|
|
17419
|
+
fingerprint,
|
|
17420
|
+
assessment: {
|
|
17421
|
+
reversibility: "reversible",
|
|
17422
|
+
external: params.locationLabel === "outside_repo",
|
|
17423
|
+
blastRadius: params.locationLabel === "outside_repo" ? "outside the repository" : "sensitive repository file",
|
|
17424
|
+
confidence: 0.85,
|
|
17425
|
+
signals: [...params.signals, ...decision.signals]
|
|
17426
|
+
},
|
|
17427
|
+
capabilityRequests: [request],
|
|
17428
|
+
authorizationDecision: decision,
|
|
17429
|
+
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17430
|
+
};
|
|
17431
|
+
}
|
|
17432
|
+
return {
|
|
17433
|
+
verdict: "allow",
|
|
17434
|
+
reason: "effect.fs_read",
|
|
17435
|
+
summary: params.filePath,
|
|
17436
|
+
fingerprint,
|
|
17437
|
+
assessment: {
|
|
17438
|
+
reversibility: "reversible",
|
|
17439
|
+
external: false,
|
|
17440
|
+
blastRadius: "tool scope",
|
|
17441
|
+
confidence: 0.88,
|
|
17442
|
+
signals: [...params.signals, ...decision.signals]
|
|
17443
|
+
},
|
|
17444
|
+
capabilityRequests: [request],
|
|
17445
|
+
authorizationDecision: decision,
|
|
17446
|
+
boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
|
|
17447
|
+
};
|
|
17448
|
+
}
|
|
17449
|
+
function classifyFilePathMutation(params) {
|
|
17450
|
+
const {
|
|
17451
|
+
toolName,
|
|
17452
|
+
toolKind,
|
|
17453
|
+
filePath,
|
|
17454
|
+
repoRoot,
|
|
17455
|
+
cwd,
|
|
17456
|
+
config,
|
|
17457
|
+
options,
|
|
17458
|
+
sensitivePaths,
|
|
17459
|
+
protectedRoots,
|
|
17460
|
+
isDelete
|
|
17461
|
+
} = params;
|
|
17462
|
+
const signals = [];
|
|
17463
|
+
const resolvedPath = path51.isAbsolute(filePath) ? filePath : path51.resolve(cwd, filePath);
|
|
17464
|
+
const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
|
|
17465
|
+
if (hitsProtectedRoot) {
|
|
17466
|
+
signals.push("control_plane_path");
|
|
17467
|
+
return classifyFileMutationWithPolicy({
|
|
17468
|
+
toolName,
|
|
17469
|
+
toolKind,
|
|
17470
|
+
filePath,
|
|
17471
|
+
resolvedPath,
|
|
17472
|
+
repoRoot,
|
|
17473
|
+
cwd,
|
|
17474
|
+
config,
|
|
17475
|
+
options,
|
|
17476
|
+
signals,
|
|
17477
|
+
isDelete,
|
|
17478
|
+
locationLabel: "control_plane"
|
|
17479
|
+
});
|
|
17480
|
+
}
|
|
17481
|
+
const workspaceMatch = resolveWorkspaceRootMatch(
|
|
17482
|
+
repoRoot,
|
|
17483
|
+
options.trustedWorkspaceRoots,
|
|
17484
|
+
resolvedPath
|
|
17485
|
+
);
|
|
17486
|
+
if (workspaceMatch === null) {
|
|
17487
|
+
signals.push("outside_repo_path");
|
|
17488
|
+
return classifyFileMutationWithPolicy({
|
|
17489
|
+
toolName,
|
|
17490
|
+
toolKind,
|
|
17491
|
+
filePath,
|
|
17492
|
+
resolvedPath,
|
|
17493
|
+
repoRoot,
|
|
17494
|
+
cwd,
|
|
17495
|
+
config,
|
|
17496
|
+
options,
|
|
17497
|
+
signals,
|
|
17498
|
+
isDelete,
|
|
17499
|
+
locationLabel: "outside_repo"
|
|
17500
|
+
});
|
|
17501
|
+
}
|
|
17502
|
+
if (workspaceMatch.kind === "trusted") {
|
|
17503
|
+
signals.push("trusted_workspace_root");
|
|
17504
|
+
}
|
|
17505
|
+
const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
|
|
17506
|
+
const workspacePrescan = mutationPrescanRequiresAsk({
|
|
17507
|
+
targets: [filePath],
|
|
17508
|
+
cwd,
|
|
17509
|
+
repoRoot,
|
|
17510
|
+
trustedCwd,
|
|
17511
|
+
trustedWorkspaceRoots: options.trustedWorkspaceRoots,
|
|
17512
|
+
sensitivePaths
|
|
17513
|
+
});
|
|
17514
|
+
if (workspacePrescan) {
|
|
17515
|
+
return classifyFileMutationWithPolicy({
|
|
17516
|
+
toolName,
|
|
17517
|
+
toolKind,
|
|
17518
|
+
filePath,
|
|
17519
|
+
resolvedPath,
|
|
17520
|
+
repoRoot,
|
|
17521
|
+
cwd,
|
|
17522
|
+
config,
|
|
17523
|
+
options,
|
|
17524
|
+
signals,
|
|
17525
|
+
isDelete,
|
|
17526
|
+
locationLabel: "sensitive_path"
|
|
17527
|
+
});
|
|
17528
|
+
}
|
|
17529
|
+
const relativePath = workspaceMatch.relativePath;
|
|
17530
|
+
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
17531
|
+
signals.push("sensitive_path");
|
|
17532
|
+
return classifyFileMutationWithPolicy({
|
|
17533
|
+
toolName,
|
|
17534
|
+
toolKind,
|
|
17535
|
+
filePath,
|
|
17536
|
+
resolvedPath,
|
|
17537
|
+
repoRoot,
|
|
17538
|
+
cwd,
|
|
17539
|
+
config,
|
|
17540
|
+
options,
|
|
17541
|
+
signals,
|
|
17542
|
+
isDelete,
|
|
17543
|
+
locationLabel: "sensitive_path"
|
|
17544
|
+
});
|
|
17545
|
+
}
|
|
17546
|
+
if (isDelete) {
|
|
17547
|
+
if (isGitPath(resolvedPath, repoRoot)) {
|
|
17548
|
+
signals.push("protected_artifact");
|
|
17549
|
+
return classifyFileMutationWithPolicy({
|
|
17550
|
+
toolName,
|
|
17551
|
+
toolKind,
|
|
17552
|
+
filePath,
|
|
17553
|
+
resolvedPath,
|
|
17554
|
+
repoRoot,
|
|
17555
|
+
cwd,
|
|
17556
|
+
config,
|
|
17557
|
+
options,
|
|
17558
|
+
signals,
|
|
17559
|
+
isDelete: true,
|
|
17560
|
+
locationLabel: "sensitive_path"
|
|
17561
|
+
});
|
|
17562
|
+
}
|
|
17563
|
+
signals.push("file_delete");
|
|
17564
|
+
return classifyFileMutationWithPolicy({
|
|
17565
|
+
toolName,
|
|
17566
|
+
toolKind,
|
|
17567
|
+
filePath,
|
|
17568
|
+
resolvedPath,
|
|
17569
|
+
repoRoot,
|
|
17570
|
+
cwd,
|
|
17571
|
+
config,
|
|
17572
|
+
options,
|
|
17573
|
+
signals,
|
|
17574
|
+
isDelete: true,
|
|
17575
|
+
locationLabel: "repo_local"
|
|
17576
|
+
});
|
|
17577
|
+
}
|
|
17578
|
+
signals.push("file_mutation");
|
|
17579
|
+
return classifyFileMutationWithPolicy({
|
|
17580
|
+
toolName,
|
|
17581
|
+
toolKind,
|
|
17582
|
+
filePath,
|
|
17583
|
+
resolvedPath,
|
|
17584
|
+
repoRoot,
|
|
17585
|
+
cwd,
|
|
17586
|
+
config,
|
|
17587
|
+
options,
|
|
17588
|
+
signals,
|
|
17589
|
+
isDelete: false,
|
|
17590
|
+
locationLabel: "repo_local"
|
|
17591
|
+
});
|
|
17592
|
+
}
|
|
17593
|
+
function classifyFilePathRead(params) {
|
|
17594
|
+
const {
|
|
17595
|
+
toolName,
|
|
17596
|
+
toolKind,
|
|
17597
|
+
filePath,
|
|
17598
|
+
repoRoot,
|
|
17599
|
+
cwd,
|
|
17600
|
+
config,
|
|
17601
|
+
options,
|
|
17602
|
+
sensitivePaths,
|
|
17603
|
+
protectedRoots
|
|
17604
|
+
} = params;
|
|
17605
|
+
const signals = ["effect.fs_read"];
|
|
17606
|
+
const resolvedPath = path51.isAbsolute(filePath) ? filePath : path51.resolve(cwd, filePath);
|
|
17607
|
+
if (protectedRoots.some((root) => pathWithinRoot(root, resolvedPath))) {
|
|
17608
|
+
signals.push("control_plane_path");
|
|
17609
|
+
return classifyFileReadWithPolicy({
|
|
17610
|
+
toolName,
|
|
17611
|
+
toolKind,
|
|
17612
|
+
filePath,
|
|
17613
|
+
resolvedPath,
|
|
17614
|
+
repoRoot,
|
|
17615
|
+
cwd,
|
|
17616
|
+
config,
|
|
17617
|
+
options,
|
|
17618
|
+
signals,
|
|
17619
|
+
locationLabel: "control_plane"
|
|
17620
|
+
});
|
|
17621
|
+
}
|
|
17622
|
+
const workspaceMatch = resolveWorkspaceRootMatch(
|
|
17623
|
+
repoRoot,
|
|
17624
|
+
options.trustedWorkspaceRoots,
|
|
17625
|
+
resolvedPath
|
|
17626
|
+
);
|
|
17627
|
+
if (workspaceMatch === null) {
|
|
17628
|
+
signals.push("outside_repo_path");
|
|
17629
|
+
return classifyFileReadWithPolicy({
|
|
17630
|
+
toolName,
|
|
17631
|
+
toolKind,
|
|
17632
|
+
filePath,
|
|
17633
|
+
resolvedPath,
|
|
17634
|
+
repoRoot,
|
|
17635
|
+
cwd,
|
|
17636
|
+
config,
|
|
17637
|
+
options,
|
|
17638
|
+
signals,
|
|
17639
|
+
locationLabel: "outside_repo"
|
|
17640
|
+
});
|
|
17641
|
+
}
|
|
17642
|
+
const relativePath = workspaceMatch.relativePath;
|
|
17643
|
+
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
17644
|
+
signals.push("sensitive_path");
|
|
17645
|
+
return classifyFileReadWithPolicy({
|
|
17646
|
+
toolName,
|
|
17647
|
+
toolKind,
|
|
17648
|
+
filePath,
|
|
17649
|
+
resolvedPath,
|
|
17650
|
+
repoRoot,
|
|
17651
|
+
cwd,
|
|
17652
|
+
config,
|
|
17653
|
+
options,
|
|
17654
|
+
signals,
|
|
17655
|
+
locationLabel: "sensitive_path"
|
|
17656
|
+
});
|
|
17657
|
+
}
|
|
17658
|
+
return classifyFileReadWithPolicy({
|
|
17659
|
+
toolName,
|
|
17660
|
+
toolKind,
|
|
17661
|
+
filePath,
|
|
17662
|
+
resolvedPath,
|
|
17663
|
+
repoRoot,
|
|
17664
|
+
cwd,
|
|
17665
|
+
config,
|
|
17666
|
+
options,
|
|
17667
|
+
signals,
|
|
17668
|
+
locationLabel: "repo_local"
|
|
17669
|
+
});
|
|
17670
|
+
}
|
|
17671
|
+
function indeterminateToolResult(toolName, payload, repoRoot, options) {
|
|
17672
|
+
const summary = canonicalStringify(scrubPayload(payload.tool_input ?? {}, options));
|
|
17673
|
+
const fingerprint = toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot);
|
|
17674
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17675
|
+
return {
|
|
17676
|
+
verdict: "deny_pending_approval",
|
|
17677
|
+
reason: "indeterminate_tool_effect",
|
|
17678
|
+
summary,
|
|
17679
|
+
fingerprint,
|
|
17680
|
+
assessment: {
|
|
17681
|
+
reversibility: "irreversible",
|
|
17682
|
+
external: false,
|
|
17683
|
+
blastRadius: "unknown tool action",
|
|
17684
|
+
confidence: 0.5,
|
|
17685
|
+
signals: ["indeterminate_tool_effect"]
|
|
17686
|
+
}
|
|
17687
|
+
};
|
|
17688
|
+
}
|
|
17689
|
+
return {
|
|
17690
|
+
verdict: "allow_flagged",
|
|
17691
|
+
reason: "indeterminate_tool_effect",
|
|
17692
|
+
summary,
|
|
17693
|
+
fingerprint,
|
|
17694
|
+
assessment: {
|
|
17695
|
+
reversibility: "recoverable_with_cost",
|
|
17696
|
+
external: false,
|
|
17697
|
+
blastRadius: "tool scope",
|
|
17698
|
+
confidence: 0.5,
|
|
17699
|
+
signals: ["indeterminate_tool_effect"]
|
|
17700
|
+
}
|
|
17701
|
+
};
|
|
17702
|
+
}
|
|
17703
|
+
async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
17704
|
+
const toolName = String(payload.tool_name ?? "");
|
|
17705
|
+
const toolKind = normalizedToolName(toolName);
|
|
17706
|
+
const sensitivePaths = [...DEFAULT_SENSITIVE_PATHS, ...options.sensitivePaths ?? []];
|
|
17707
|
+
const protectedRoots = [
|
|
17708
|
+
...options.protectedArtifactRoots ?? [],
|
|
17709
|
+
...options.controlPlaneDir ? [options.controlPlaneDir] : []
|
|
17710
|
+
];
|
|
17711
|
+
if (toolKind === "shell") {
|
|
17712
|
+
const command = extractShellCommand(payload);
|
|
17713
|
+
if (!command) {
|
|
17714
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17715
|
+
return {
|
|
17716
|
+
verdict: "deny_pending_approval",
|
|
17717
|
+
reason: "tool_shell_missing_command",
|
|
17718
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17719
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17720
|
+
assessment: {
|
|
17721
|
+
reversibility: "irreversible",
|
|
17722
|
+
external: false,
|
|
17723
|
+
blastRadius: "tool shell",
|
|
17724
|
+
confidence: 0.85,
|
|
17725
|
+
signals: ["missing_command"]
|
|
17726
|
+
}
|
|
17727
|
+
};
|
|
17728
|
+
}
|
|
17729
|
+
return {
|
|
17730
|
+
verdict: "allow_flagged",
|
|
17731
|
+
reason: "tool_shell_missing_command",
|
|
17732
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17733
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17734
|
+
assessment: {
|
|
17735
|
+
reversibility: "recoverable_with_cost",
|
|
17736
|
+
external: false,
|
|
17737
|
+
blastRadius: "tool shell",
|
|
17738
|
+
confidence: 0.5,
|
|
17739
|
+
signals: ["missing_command"]
|
|
17740
|
+
}
|
|
17741
|
+
};
|
|
17742
|
+
}
|
|
17743
|
+
const shellResult = await classifyShell(command, cwd, repoRoot, config, options);
|
|
17744
|
+
return {
|
|
17745
|
+
...shellResult,
|
|
17746
|
+
summary: command
|
|
17747
|
+
};
|
|
17748
|
+
}
|
|
17749
|
+
const effect = inferToolPayloadEffect(payload, toolName);
|
|
17750
|
+
if (effect === "file_mutation" || effect === "file_delete") {
|
|
17751
|
+
const filePath2 = extractFilePath(payload);
|
|
17752
|
+
if (!filePath2) {
|
|
17753
|
+
if (options.unknownLocalEffect === "deny") {
|
|
17263
17754
|
return {
|
|
17264
17755
|
verdict: "deny_pending_approval",
|
|
17265
17756
|
reason: "file_mutation_missing_path",
|
|
@@ -17288,138 +17779,20 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17288
17779
|
}
|
|
17289
17780
|
};
|
|
17290
17781
|
}
|
|
17291
|
-
|
|
17292
|
-
const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
|
|
17293
|
-
const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
|
|
17294
|
-
if (hitsProtectedRoot) {
|
|
17295
|
-
signals.push("control_plane_path");
|
|
17296
|
-
return classifyFileMutationWithPolicy({
|
|
17297
|
-
toolName,
|
|
17298
|
-
toolKind,
|
|
17299
|
-
filePath,
|
|
17300
|
-
resolvedPath,
|
|
17301
|
-
repoRoot,
|
|
17302
|
-
cwd,
|
|
17303
|
-
config,
|
|
17304
|
-
options,
|
|
17305
|
-
signals,
|
|
17306
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17307
|
-
locationLabel: "control_plane"
|
|
17308
|
-
});
|
|
17309
|
-
}
|
|
17310
|
-
const workspaceMatch = resolveWorkspaceRootMatch(
|
|
17311
|
-
repoRoot,
|
|
17312
|
-
options.trustedWorkspaceRoots,
|
|
17313
|
-
resolvedPath
|
|
17314
|
-
);
|
|
17315
|
-
if (workspaceMatch === null) {
|
|
17316
|
-
signals.push("outside_repo_path");
|
|
17317
|
-
return classifyFileMutationWithPolicy({
|
|
17318
|
-
toolName,
|
|
17319
|
-
toolKind,
|
|
17320
|
-
filePath,
|
|
17321
|
-
resolvedPath,
|
|
17322
|
-
repoRoot,
|
|
17323
|
-
cwd,
|
|
17324
|
-
config,
|
|
17325
|
-
options,
|
|
17326
|
-
signals,
|
|
17327
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17328
|
-
locationLabel: "outside_repo"
|
|
17329
|
-
});
|
|
17330
|
-
}
|
|
17331
|
-
if (workspaceMatch.kind === "trusted") {
|
|
17332
|
-
signals.push("trusted_workspace_root");
|
|
17333
|
-
}
|
|
17334
|
-
const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
|
|
17335
|
-
const workspacePrescan = mutationPrescanRequiresAsk({
|
|
17336
|
-
targets: [filePath],
|
|
17337
|
-
cwd,
|
|
17338
|
-
repoRoot,
|
|
17339
|
-
trustedCwd,
|
|
17340
|
-
trustedWorkspaceRoots: options.trustedWorkspaceRoots,
|
|
17341
|
-
sensitivePaths
|
|
17342
|
-
});
|
|
17343
|
-
if (workspacePrescan) {
|
|
17344
|
-
return classifyFileMutationWithPolicy({
|
|
17345
|
-
toolName,
|
|
17346
|
-
toolKind,
|
|
17347
|
-
filePath,
|
|
17348
|
-
resolvedPath,
|
|
17349
|
-
repoRoot,
|
|
17350
|
-
cwd,
|
|
17351
|
-
config,
|
|
17352
|
-
options,
|
|
17353
|
-
signals,
|
|
17354
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17355
|
-
locationLabel: "sensitive_path"
|
|
17356
|
-
});
|
|
17357
|
-
}
|
|
17358
|
-
const relativePath = workspaceMatch.relativePath;
|
|
17359
|
-
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
17360
|
-
signals.push("sensitive_path");
|
|
17361
|
-
return classifyFileMutationWithPolicy({
|
|
17362
|
-
toolName,
|
|
17363
|
-
toolKind,
|
|
17364
|
-
filePath,
|
|
17365
|
-
resolvedPath,
|
|
17366
|
-
repoRoot,
|
|
17367
|
-
cwd,
|
|
17368
|
-
config,
|
|
17369
|
-
options,
|
|
17370
|
-
signals,
|
|
17371
|
-
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
17372
|
-
locationLabel: "sensitive_path"
|
|
17373
|
-
});
|
|
17374
|
-
}
|
|
17375
|
-
if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
|
|
17376
|
-
if (isGitPath(resolvedPath, repoRoot)) {
|
|
17377
|
-
signals.push("protected_artifact");
|
|
17378
|
-
return classifyFileMutationWithPolicy({
|
|
17379
|
-
toolName,
|
|
17380
|
-
toolKind,
|
|
17381
|
-
filePath,
|
|
17382
|
-
resolvedPath,
|
|
17383
|
-
repoRoot,
|
|
17384
|
-
cwd,
|
|
17385
|
-
config,
|
|
17386
|
-
options,
|
|
17387
|
-
signals,
|
|
17388
|
-
isDelete: true,
|
|
17389
|
-
locationLabel: "sensitive_path"
|
|
17390
|
-
});
|
|
17391
|
-
}
|
|
17392
|
-
signals.push("file_delete");
|
|
17393
|
-
return classifyFileMutationWithPolicy({
|
|
17394
|
-
toolName,
|
|
17395
|
-
toolKind,
|
|
17396
|
-
filePath,
|
|
17397
|
-
resolvedPath,
|
|
17398
|
-
repoRoot,
|
|
17399
|
-
cwd,
|
|
17400
|
-
config,
|
|
17401
|
-
options,
|
|
17402
|
-
signals,
|
|
17403
|
-
isDelete: true,
|
|
17404
|
-
locationLabel: "repo_local"
|
|
17405
|
-
});
|
|
17406
|
-
}
|
|
17407
|
-
signals.push("file_mutation");
|
|
17408
|
-
return classifyFileMutationWithPolicy({
|
|
17782
|
+
return classifyFilePathMutation({
|
|
17409
17783
|
toolName,
|
|
17410
17784
|
toolKind,
|
|
17411
|
-
filePath,
|
|
17412
|
-
resolvedPath,
|
|
17785
|
+
filePath: filePath2,
|
|
17413
17786
|
repoRoot,
|
|
17414
17787
|
cwd,
|
|
17415
17788
|
config,
|
|
17416
17789
|
options,
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17790
|
+
sensitivePaths,
|
|
17791
|
+
protectedRoots,
|
|
17792
|
+
isDelete: effect === "file_delete"
|
|
17420
17793
|
});
|
|
17421
17794
|
}
|
|
17422
|
-
if (
|
|
17795
|
+
if (effect === "apply_patch") {
|
|
17423
17796
|
const patch = extractPatch(payload);
|
|
17424
17797
|
const targets = patch ? applyPatchTargets2(patch) : [];
|
|
17425
17798
|
if (targets.length === 0) {
|
|
@@ -17457,7 +17830,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17457
17830
|
const result = await classifyToolUse(
|
|
17458
17831
|
{
|
|
17459
17832
|
tool_name: target.delete ? "Delete" : "Write",
|
|
17460
|
-
tool_input: { path: target.path }
|
|
17833
|
+
tool_input: target.delete ? { path: target.path } : { path: target.path, contents: " " }
|
|
17461
17834
|
},
|
|
17462
17835
|
repoRoot,
|
|
17463
17836
|
cwd,
|
|
@@ -17483,19 +17856,54 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
|
17483
17856
|
}
|
|
17484
17857
|
};
|
|
17485
17858
|
}
|
|
17486
|
-
|
|
17487
|
-
|
|
17488
|
-
|
|
17489
|
-
|
|
17490
|
-
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17491
|
-
assessment: {
|
|
17492
|
-
reversibility: "reversible",
|
|
17493
|
-
external: false,
|
|
17494
|
-
blastRadius: "tool scope",
|
|
17495
|
-
confidence: 0.5,
|
|
17496
|
-
signals: ["unclassified_tool"]
|
|
17859
|
+
if (effect === "file_read") {
|
|
17860
|
+
const filePath2 = extractFilePath(payload);
|
|
17861
|
+
if (!filePath2) {
|
|
17862
|
+
return indeterminateToolResult(toolName, payload, repoRoot, options);
|
|
17497
17863
|
}
|
|
17498
|
-
|
|
17864
|
+
return classifyFilePathRead({
|
|
17865
|
+
toolName,
|
|
17866
|
+
toolKind,
|
|
17867
|
+
filePath: filePath2,
|
|
17868
|
+
repoRoot,
|
|
17869
|
+
cwd,
|
|
17870
|
+
config,
|
|
17871
|
+
options,
|
|
17872
|
+
sensitivePaths,
|
|
17873
|
+
protectedRoots
|
|
17874
|
+
});
|
|
17875
|
+
}
|
|
17876
|
+
if (effect === "search_read") {
|
|
17877
|
+
return {
|
|
17878
|
+
verdict: "allow",
|
|
17879
|
+
reason: "effect.search_read",
|
|
17880
|
+
summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
|
|
17881
|
+
fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
|
|
17882
|
+
assessment: {
|
|
17883
|
+
reversibility: "reversible",
|
|
17884
|
+
external: false,
|
|
17885
|
+
blastRadius: "tool scope",
|
|
17886
|
+
confidence: 0.88,
|
|
17887
|
+
signals: ["effect.search_read"]
|
|
17888
|
+
}
|
|
17889
|
+
};
|
|
17890
|
+
}
|
|
17891
|
+
const filePath = extractFilePath(payload);
|
|
17892
|
+
if (filePath) {
|
|
17893
|
+
return classifyFilePathMutation({
|
|
17894
|
+
toolName,
|
|
17895
|
+
toolKind,
|
|
17896
|
+
filePath,
|
|
17897
|
+
repoRoot,
|
|
17898
|
+
cwd,
|
|
17899
|
+
config,
|
|
17900
|
+
options,
|
|
17901
|
+
sensitivePaths,
|
|
17902
|
+
protectedRoots,
|
|
17903
|
+
isDelete: toolKind === "delete"
|
|
17904
|
+
});
|
|
17905
|
+
}
|
|
17906
|
+
return indeterminateToolResult(toolName, payload, repoRoot, options);
|
|
17499
17907
|
}
|
|
17500
17908
|
|
|
17501
17909
|
// src/core/gate-engine.ts
|
|
@@ -17830,15 +18238,8 @@ function gateEnabledForAction(config, action) {
|
|
|
17830
18238
|
if (action.kind === "subagent") {
|
|
17831
18239
|
return config.gates.subagent;
|
|
17832
18240
|
}
|
|
17833
|
-
|
|
17834
|
-
|
|
17835
|
-
return config.gates.toolShell;
|
|
17836
|
-
}
|
|
17837
|
-
if (toolName === "Write" || toolName === "StrReplace" || toolName === "Delete") {
|
|
17838
|
-
return config.gates.fileMutation;
|
|
17839
|
-
}
|
|
17840
|
-
if (toolName === "Task") {
|
|
17841
|
-
return config.gates.subagent;
|
|
18241
|
+
if (action.kind === "tool") {
|
|
18242
|
+
return config.gates.fileMutation || config.gates.toolShell;
|
|
17842
18243
|
}
|
|
17843
18244
|
return true;
|
|
17844
18245
|
}
|
|
@@ -17875,7 +18276,7 @@ function hashDecisionConfig(config) {
|
|
|
17875
18276
|
init_fingerprint2();
|
|
17876
18277
|
|
|
17877
18278
|
// src/version.ts
|
|
17878
|
-
var PACKAGE_VERSION = "0.
|
|
18279
|
+
var PACKAGE_VERSION = "0.10.1";
|
|
17879
18280
|
|
|
17880
18281
|
// src/runtime-provenance.ts
|
|
17881
18282
|
function resolveRuntimeArtifactHash(artifactHash) {
|
|
@@ -17948,56 +18349,56 @@ function capabilityRequestsBlockRecovery(requests) {
|
|
|
17948
18349
|
init_fingerprint2();
|
|
17949
18350
|
init_path_utils();
|
|
17950
18351
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
17951
|
-
import { existsSync as
|
|
17952
|
-
import { mkdir as
|
|
17953
|
-
import
|
|
18352
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
18353
|
+
import { mkdir as mkdir12, readdir as readdir3, readFile as readFile14, rename as rename4, rm as rm6 } from "node:fs/promises";
|
|
18354
|
+
import path56 from "node:path";
|
|
17954
18355
|
|
|
17955
18356
|
// src/core/recovery/artifact-store.ts
|
|
17956
18357
|
init_fingerprint2();
|
|
17957
18358
|
init_path_utils();
|
|
17958
18359
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
17959
|
-
import { existsSync as
|
|
17960
|
-
import { lstat as lstat7, mkdir as
|
|
17961
|
-
import
|
|
18360
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
18361
|
+
import { lstat as lstat7, mkdir as mkdir11, open as open6, readdir as readdir2, readFile as readFile11, rename as rename3, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
|
|
18362
|
+
import path53 from "node:path";
|
|
17962
18363
|
|
|
17963
18364
|
// src/core/recovery/snapshot-node.ts
|
|
17964
18365
|
init_fingerprint2();
|
|
17965
18366
|
init_path_utils();
|
|
17966
18367
|
import { createHash as createHash12 } from "node:crypto";
|
|
17967
|
-
import { existsSync as
|
|
18368
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
17968
18369
|
import {
|
|
17969
|
-
chmod as
|
|
18370
|
+
chmod as chmod4,
|
|
17970
18371
|
copyFile as copyFile2,
|
|
17971
18372
|
lstat as lstat6,
|
|
17972
|
-
mkdir as
|
|
17973
|
-
open as
|
|
17974
|
-
readFile as
|
|
18373
|
+
mkdir as mkdir10,
|
|
18374
|
+
open as open5,
|
|
18375
|
+
readFile as readFile10,
|
|
17975
18376
|
readlink as readlink4,
|
|
17976
18377
|
rm as rm4,
|
|
17977
18378
|
rmdir as rmdir2,
|
|
17978
18379
|
symlink as symlink3,
|
|
17979
18380
|
writeFile as writeFile6
|
|
17980
18381
|
} from "node:fs/promises";
|
|
17981
|
-
import
|
|
18382
|
+
import path52 from "node:path";
|
|
17982
18383
|
var RECOVERY_UNSUPPORTED_FILE_KIND = "recovery_unsupported_file_kind";
|
|
17983
18384
|
function validRecoveryRelativePath(relativePath) {
|
|
17984
|
-
if (!relativePath || relativePath.includes("\0") ||
|
|
17985
|
-
const normalized =
|
|
17986
|
-
return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${
|
|
18385
|
+
if (!relativePath || relativePath.includes("\0") || path52.isAbsolute(relativePath)) return false;
|
|
18386
|
+
const normalized = path52.normalize(relativePath);
|
|
18387
|
+
return normalized !== "." && normalized !== ".." && !normalized.startsWith(`..${path52.sep}`);
|
|
17987
18388
|
}
|
|
17988
18389
|
async function assertRecoverySafeTarget(resourceRoot, relativePath) {
|
|
17989
18390
|
if (!validRecoveryRelativePath(relativePath)) throw new Error("recovery_path_escape");
|
|
17990
18391
|
const root = canonicalPath(resourceRoot);
|
|
17991
|
-
const target =
|
|
17992
|
-
const relative =
|
|
17993
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
18392
|
+
const target = path52.resolve(root, relativePath);
|
|
18393
|
+
const relative = path52.relative(root, target);
|
|
18394
|
+
if (relative === ".." || relative.startsWith(`..${path52.sep}`) || path52.isAbsolute(relative)) {
|
|
17994
18395
|
throw new Error("recovery_path_escape");
|
|
17995
18396
|
}
|
|
17996
18397
|
let current = root;
|
|
17997
|
-
const parentParts =
|
|
18398
|
+
const parentParts = path52.relative(root, path52.dirname(target)).split(path52.sep).filter(Boolean);
|
|
17998
18399
|
for (const part of parentParts) {
|
|
17999
|
-
current =
|
|
18000
|
-
if (!
|
|
18400
|
+
current = path52.join(current, part);
|
|
18401
|
+
if (!existsSync13(current)) break;
|
|
18001
18402
|
const info = await lstat6(current);
|
|
18002
18403
|
if (info.isSymbolicLink()) throw new Error("recovery_symlink_escape");
|
|
18003
18404
|
if (!info.isDirectory()) break;
|
|
@@ -18005,7 +18406,7 @@ async function assertRecoverySafeTarget(resourceRoot, relativePath) {
|
|
|
18005
18406
|
return target;
|
|
18006
18407
|
}
|
|
18007
18408
|
async function fsyncPath(filePath) {
|
|
18008
|
-
const handle = await
|
|
18409
|
+
const handle = await open5(filePath, "r");
|
|
18009
18410
|
try {
|
|
18010
18411
|
await handle.sync();
|
|
18011
18412
|
} finally {
|
|
@@ -18047,13 +18448,13 @@ async function captureRecoverySnapshot(filePath, options) {
|
|
|
18047
18448
|
return { kind: "directory", mode, hash: recoveryDirectoryHash(mode) };
|
|
18048
18449
|
}
|
|
18049
18450
|
if (!info.isFile()) throw new Error(RECOVERY_UNSUPPORTED_FILE_KIND);
|
|
18050
|
-
const content = await
|
|
18451
|
+
const content = await readFile10(filePath);
|
|
18051
18452
|
const hash = createHash12("sha256").update(content).digest("hex");
|
|
18052
18453
|
let blob;
|
|
18053
18454
|
if (options?.blobDir) {
|
|
18054
|
-
await
|
|
18055
|
-
const blobPath =
|
|
18056
|
-
if (!
|
|
18455
|
+
await mkdir10(options.blobDir, { recursive: true, mode: 448 });
|
|
18456
|
+
const blobPath = path52.join(options.blobDir, hash);
|
|
18457
|
+
if (!existsSync13(blobPath)) {
|
|
18057
18458
|
await writeFile6(blobPath, content, { mode: 384 });
|
|
18058
18459
|
await fsyncPath(blobPath);
|
|
18059
18460
|
}
|
|
@@ -18107,7 +18508,7 @@ async function validateRecoverySnapshot(params) {
|
|
|
18107
18508
|
if (record.blob !== `blobs/${record.hash}`) throw new Error(params.corruptReason);
|
|
18108
18509
|
let content;
|
|
18109
18510
|
try {
|
|
18110
|
-
content = await
|
|
18511
|
+
content = await readFile10(path52.join(params.artifactDir, record.blob));
|
|
18111
18512
|
} catch {
|
|
18112
18513
|
throw new Error(params.corruptReason);
|
|
18113
18514
|
}
|
|
@@ -18135,16 +18536,16 @@ var RECOVERY_STATES = /* @__PURE__ */ new Set([
|
|
|
18135
18536
|
]);
|
|
18136
18537
|
var STAGING_STALE_MS = 5 * 6e4;
|
|
18137
18538
|
function checkpointsRoot(stateDir) {
|
|
18138
|
-
return
|
|
18539
|
+
return path53.join(stateDir, "recovery", "checkpoints");
|
|
18139
18540
|
}
|
|
18140
18541
|
function checkpointDir(stateDir, checkpointId) {
|
|
18141
18542
|
if (!/^cp_[a-f0-9]{24}$/.test(checkpointId)) {
|
|
18142
18543
|
throw new Error("invalid_recovery_checkpoint_id");
|
|
18143
18544
|
}
|
|
18144
|
-
return
|
|
18545
|
+
return path53.join(checkpointsRoot(stateDir), checkpointId);
|
|
18145
18546
|
}
|
|
18146
18547
|
async function fsyncPath2(filePath) {
|
|
18147
|
-
const handle = await
|
|
18548
|
+
const handle = await open6(filePath, "r");
|
|
18148
18549
|
try {
|
|
18149
18550
|
await handle.sync();
|
|
18150
18551
|
} finally {
|
|
@@ -18152,13 +18553,13 @@ async function fsyncPath2(filePath) {
|
|
|
18152
18553
|
}
|
|
18153
18554
|
}
|
|
18154
18555
|
async function atomicWriteJson(filePath, value) {
|
|
18155
|
-
await
|
|
18556
|
+
await mkdir11(path53.dirname(filePath), { recursive: true, mode: 448 });
|
|
18156
18557
|
const temporary = `${filePath}.tmp-${randomUUID4()}`;
|
|
18157
18558
|
await writeFile7(temporary, `${JSON.stringify(value, null, 2)}
|
|
18158
18559
|
`, { mode: 384 });
|
|
18159
18560
|
await fsyncPath2(temporary);
|
|
18160
|
-
await
|
|
18161
|
-
await fsyncPath2(
|
|
18561
|
+
await rename3(temporary, filePath);
|
|
18562
|
+
await fsyncPath2(path53.dirname(filePath));
|
|
18162
18563
|
}
|
|
18163
18564
|
async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
|
|
18164
18565
|
const value = {
|
|
@@ -18168,13 +18569,13 @@ async function writeRecoveryState(artifactDir, state, manifestHash, detail) {
|
|
|
18168
18569
|
manifestHash,
|
|
18169
18570
|
...detail ? { detail } : {}
|
|
18170
18571
|
};
|
|
18171
|
-
await atomicWriteJson(
|
|
18572
|
+
await atomicWriteJson(path53.join(artifactDir, "state.json"), value);
|
|
18172
18573
|
}
|
|
18173
18574
|
async function directorySize(root) {
|
|
18174
|
-
if (!
|
|
18575
|
+
if (!existsSync14(root)) return 0;
|
|
18175
18576
|
let total = 0;
|
|
18176
18577
|
for (const entry of await readdir2(root, { withFileTypes: true })) {
|
|
18177
|
-
const entryPath =
|
|
18578
|
+
const entryPath = path53.join(root, entry.name);
|
|
18178
18579
|
if (entry.isDirectory()) total += await directorySize(entryPath);
|
|
18179
18580
|
else total += (await lstat7(entryPath)).size;
|
|
18180
18581
|
}
|
|
@@ -18219,9 +18620,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18219
18620
|
let rawManifest;
|
|
18220
18621
|
let state;
|
|
18221
18622
|
try {
|
|
18222
|
-
rawManifest = JSON.parse(await
|
|
18623
|
+
rawManifest = JSON.parse(await readFile11(path53.join(artifactDir, "manifest.json"), "utf8"));
|
|
18223
18624
|
state = JSON.parse(
|
|
18224
|
-
await
|
|
18625
|
+
await readFile11(path53.join(artifactDir, "state.json"), "utf8")
|
|
18225
18626
|
);
|
|
18226
18627
|
} catch {
|
|
18227
18628
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
@@ -18237,10 +18638,10 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18237
18638
|
}
|
|
18238
18639
|
const entryPaths = /* @__PURE__ */ new Set();
|
|
18239
18640
|
for (const entry of manifest.entries) {
|
|
18240
|
-
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(
|
|
18641
|
+
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(path53.normalize(entry.path))) {
|
|
18241
18642
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
18242
18643
|
}
|
|
18243
|
-
entryPaths.add(
|
|
18644
|
+
entryPaths.add(path53.normalize(entry.path));
|
|
18244
18645
|
for (const [side, snapshot] of [
|
|
18245
18646
|
["before", entry.before],
|
|
18246
18647
|
["after", entry.after]
|
|
@@ -18254,9 +18655,9 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18254
18655
|
});
|
|
18255
18656
|
}
|
|
18256
18657
|
}
|
|
18257
|
-
const receiptPath =
|
|
18658
|
+
const receiptPath = path53.join(artifactDir, "receipt.json");
|
|
18258
18659
|
let receipt;
|
|
18259
|
-
if (["applied", "restoring", "restored", "conflict"].includes(state.state) ||
|
|
18660
|
+
if (["applied", "restoring", "restored", "conflict"].includes(state.state) || existsSync14(receiptPath)) {
|
|
18260
18661
|
receipt = await readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
|
|
18261
18662
|
}
|
|
18262
18663
|
return { artifactDir, manifest, state, manifestHash, ...receipt ? { receipt } : {} };
|
|
@@ -18264,7 +18665,7 @@ async function readRecoveryArtifact(stateDir, checkpointId) {
|
|
|
18264
18665
|
async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
18265
18666
|
let rawReceipt;
|
|
18266
18667
|
try {
|
|
18267
|
-
rawReceipt = JSON.parse(await
|
|
18668
|
+
rawReceipt = JSON.parse(await readFile11(path53.join(artifactDir, "receipt.json"), "utf8"));
|
|
18268
18669
|
} catch {
|
|
18269
18670
|
throw new Error(RECOVERY_CHECKPOINT_CORRUPT);
|
|
18270
18671
|
}
|
|
@@ -18287,8 +18688,8 @@ async function readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHas
|
|
|
18287
18688
|
return receipt;
|
|
18288
18689
|
}
|
|
18289
18690
|
async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
18290
|
-
const receiptPath =
|
|
18291
|
-
if (
|
|
18691
|
+
const receiptPath = path53.join(artifactDir, "receipt.json");
|
|
18692
|
+
if (existsSync14(receiptPath)) {
|
|
18292
18693
|
return readAndValidateRecoveryReceipt(artifactDir, manifest, manifestHash);
|
|
18293
18694
|
}
|
|
18294
18695
|
const receipt = {
|
|
@@ -18305,14 +18706,14 @@ async function ensureRecoveryReceipt(artifactDir, manifest, manifestHash) {
|
|
|
18305
18706
|
}
|
|
18306
18707
|
async function checkpointIds(stateDir) {
|
|
18307
18708
|
const root = checkpointsRoot(stateDir);
|
|
18308
|
-
if (!
|
|
18709
|
+
if (!existsSync14(root)) return [];
|
|
18309
18710
|
return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^cp_[a-f0-9]{24}$/.test(entry.name)).map((entry) => entry.name);
|
|
18310
18711
|
}
|
|
18311
18712
|
async function artifactRepoRoot(stateDir, checkpointId) {
|
|
18312
18713
|
const artifactDir = checkpointDir(stateDir, checkpointId);
|
|
18313
18714
|
try {
|
|
18314
18715
|
const manifest = JSON.parse(
|
|
18315
|
-
await
|
|
18716
|
+
await readFile11(path53.join(artifactDir, "manifest.json"), "utf8")
|
|
18316
18717
|
);
|
|
18317
18718
|
if (typeof manifest.repoRoot === "string" && manifest.repoRoot) {
|
|
18318
18719
|
return canonicalPath(manifest.repoRoot);
|
|
@@ -18320,7 +18721,7 @@ async function artifactRepoRoot(stateDir, checkpointId) {
|
|
|
18320
18721
|
} catch {
|
|
18321
18722
|
}
|
|
18322
18723
|
try {
|
|
18323
|
-
const owner = JSON.parse(await
|
|
18724
|
+
const owner = JSON.parse(await readFile11(path53.join(artifactDir, "owner.json"), "utf8"));
|
|
18324
18725
|
return typeof owner.repoRoot === "string" && owner.repoRoot ? canonicalPath(owner.repoRoot) : null;
|
|
18325
18726
|
} catch {
|
|
18326
18727
|
return null;
|
|
@@ -18336,14 +18737,14 @@ async function checkpointIdsForRepo(stateDir, repoRoot) {
|
|
|
18336
18737
|
}
|
|
18337
18738
|
async function cleanupOrphanedStaging(stateDir) {
|
|
18338
18739
|
const root = checkpointsRoot(stateDir);
|
|
18339
|
-
if (!
|
|
18740
|
+
if (!existsSync14(root)) return;
|
|
18340
18741
|
const now = Date.now();
|
|
18341
18742
|
for (const entry of await readdir2(root, { withFileTypes: true })) {
|
|
18342
18743
|
if (!entry.isDirectory() || !/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) continue;
|
|
18343
|
-
const stagingPath =
|
|
18744
|
+
const stagingPath = path53.join(root, entry.name);
|
|
18344
18745
|
let stale = false;
|
|
18345
18746
|
try {
|
|
18346
|
-
const owner = JSON.parse(await
|
|
18747
|
+
const owner = JSON.parse(await readFile11(path53.join(stagingPath, "owner.json"), "utf8"));
|
|
18347
18748
|
const pid = typeof owner.pid === "number" ? owner.pid : Number.NaN;
|
|
18348
18749
|
const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : NaN;
|
|
18349
18750
|
let alive = false;
|
|
@@ -18383,9 +18784,9 @@ async function markRecoveryCheckpointApplied(stateDir, checkpoint) {
|
|
|
18383
18784
|
|
|
18384
18785
|
// src/core/recovery/reconcile.ts
|
|
18385
18786
|
init_fingerprint2();
|
|
18386
|
-
import { existsSync as
|
|
18387
|
-
import { readFile as
|
|
18388
|
-
import
|
|
18787
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
18788
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
18789
|
+
import path54 from "node:path";
|
|
18389
18790
|
async function matchRecoverySide(resourceRoot, entries, side) {
|
|
18390
18791
|
for (const entry of entries) {
|
|
18391
18792
|
const target = await assertRecoverySafeTarget(resourceRoot, entry.path);
|
|
@@ -18399,9 +18800,9 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
|
|
|
18399
18800
|
loaded = await readRecoveryArtifact(stateDir, checkpointId);
|
|
18400
18801
|
} catch {
|
|
18401
18802
|
const artifactDir = checkpointDir(stateDir, checkpointId);
|
|
18402
|
-
if (
|
|
18403
|
-
const manifestPath =
|
|
18404
|
-
const hash =
|
|
18803
|
+
if (existsSync15(artifactDir)) {
|
|
18804
|
+
const manifestPath = path54.join(artifactDir, "manifest.json");
|
|
18805
|
+
const hash = existsSync15(manifestPath) ? hashValue(await readFile12(manifestPath, "utf8")) : "unavailable";
|
|
18405
18806
|
await writeRecoveryState(artifactDir, "corrupt", hash, RECOVERY_CHECKPOINT_CORRUPT);
|
|
18406
18807
|
}
|
|
18407
18808
|
return "corrupt";
|
|
@@ -18436,8 +18837,8 @@ async function reconcileRecoveryCheckpoint(stateDir, checkpointId) {
|
|
|
18436
18837
|
|
|
18437
18838
|
// src/core/recovery/resource-identity.ts
|
|
18438
18839
|
init_fingerprint2();
|
|
18439
|
-
import { lstat as lstat8, readFile as
|
|
18440
|
-
import
|
|
18840
|
+
import { lstat as lstat8, readFile as readFile13, realpath as realpath3 } from "node:fs/promises";
|
|
18841
|
+
import path55 from "node:path";
|
|
18441
18842
|
async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
|
|
18442
18843
|
const resolvedRoot = await realpath3(resourceRoot);
|
|
18443
18844
|
if (resourceKind === "directory") {
|
|
@@ -18445,13 +18846,13 @@ async function currentRecoveryResourceIdentity(resourceRoot, resourceKind) {
|
|
|
18445
18846
|
if (!rootInfo.isDirectory()) throw new Error("recovery_repo_identity_unavailable");
|
|
18446
18847
|
return hashValue(`${resolvedRoot}\0${rootInfo.dev}:${rootInfo.ino}:${rootInfo.birthtimeMs}`);
|
|
18447
18848
|
}
|
|
18448
|
-
const dotGit =
|
|
18849
|
+
const dotGit = path55.join(resolvedRoot, ".git");
|
|
18449
18850
|
const gitInfo = await lstat8(dotGit);
|
|
18450
18851
|
let gitMetadataPath = dotGit;
|
|
18451
18852
|
if (gitInfo.isFile()) {
|
|
18452
|
-
const marker = (await
|
|
18853
|
+
const marker = (await readFile13(dotGit, "utf8")).trim();
|
|
18453
18854
|
if (!marker.startsWith("gitdir:")) throw new Error("recovery_repo_identity_unavailable");
|
|
18454
|
-
gitMetadataPath =
|
|
18855
|
+
gitMetadataPath = path55.resolve(resolvedRoot, marker.slice("gitdir:".length).trim());
|
|
18455
18856
|
} else if (!gitInfo.isDirectory()) {
|
|
18456
18857
|
throw new Error("recovery_repo_identity_unavailable");
|
|
18457
18858
|
}
|
|
@@ -18516,7 +18917,7 @@ async function garbageCollect(stateDir, config, repoRoot) {
|
|
|
18516
18917
|
async function prepareRecoveryCheckpoint(params) {
|
|
18517
18918
|
const backend = params.backend ?? "git_worktree";
|
|
18518
18919
|
const resourceKind = await resolveRecoveryResourceKind(backend, params.repoRoot);
|
|
18519
|
-
await
|
|
18920
|
+
await mkdir12(checkpointsRoot(params.stateDir), { recursive: true, mode: 448 });
|
|
18520
18921
|
await cleanupOrphanedStaging(params.stateDir);
|
|
18521
18922
|
await garbageCollect(params.stateDir, params.config, params.repoRoot);
|
|
18522
18923
|
const existing = await checkpointIdsForRepo(params.stateDir, params.repoRoot);
|
|
@@ -18524,16 +18925,16 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18524
18925
|
throw new Error(RECOVERY_CHECKPOINT_QUOTA);
|
|
18525
18926
|
}
|
|
18526
18927
|
const checkpointId = `cp_${randomUUID5().replaceAll("-", "").slice(0, 24)}`;
|
|
18527
|
-
const temporary =
|
|
18928
|
+
const temporary = path56.join(checkpointsRoot(params.stateDir), `.tmp-${checkpointId}`);
|
|
18528
18929
|
const finalDir = checkpointDir(params.stateDir, checkpointId);
|
|
18529
|
-
await
|
|
18530
|
-
await atomicWriteJson(
|
|
18930
|
+
await mkdir12(temporary, { recursive: true, mode: 448 });
|
|
18931
|
+
await atomicWriteJson(path56.join(temporary, "owner.json"), {
|
|
18531
18932
|
version: 1,
|
|
18532
18933
|
pid: process.pid,
|
|
18533
18934
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18534
18935
|
repoRoot: canonicalPath(params.repoRoot)
|
|
18535
18936
|
});
|
|
18536
|
-
await
|
|
18937
|
+
await mkdir12(path56.join(temporary, "blobs"), { recursive: true, mode: 448 });
|
|
18537
18938
|
try {
|
|
18538
18939
|
const entries = [];
|
|
18539
18940
|
const protectedRoots = (params.protectedRoots ?? []).map(canonicalPath);
|
|
@@ -18542,8 +18943,8 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18542
18943
|
)) {
|
|
18543
18944
|
const target = await assertRecoverySafeTarget(params.repoRoot, change.relativePath);
|
|
18544
18945
|
if (protectedRoots.some((root) => {
|
|
18545
|
-
const relative =
|
|
18546
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
18946
|
+
const relative = path56.relative(root, target);
|
|
18947
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path56.sep}`) && !path56.isAbsolute(relative);
|
|
18547
18948
|
})) {
|
|
18548
18949
|
throw new Error("recovery_protected_path");
|
|
18549
18950
|
}
|
|
@@ -18555,7 +18956,7 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18555
18956
|
entries.push({
|
|
18556
18957
|
path: change.relativePath,
|
|
18557
18958
|
before: await captureRecoverySnapshot(baseline, {
|
|
18558
|
-
blobDir:
|
|
18959
|
+
blobDir: path56.join(temporary, "blobs")
|
|
18559
18960
|
}),
|
|
18560
18961
|
after: withoutRecoveryBlob(await captureRecoverySnapshot(source))
|
|
18561
18962
|
});
|
|
@@ -18592,12 +18993,12 @@ async function prepareRecoveryCheckpoint(params) {
|
|
|
18592
18993
|
entries
|
|
18593
18994
|
};
|
|
18594
18995
|
const manifestHash = hashValue(canonicalStringify(manifest));
|
|
18595
|
-
await atomicWriteJson(
|
|
18996
|
+
await atomicWriteJson(path56.join(temporary, "manifest.json"), manifest);
|
|
18596
18997
|
await writeRecoveryState(temporary, "prepared", manifestHash);
|
|
18597
18998
|
await fsyncPath2(temporary);
|
|
18598
18999
|
const projectedBytes = await recoveryCheckpointStorageBytes(params.stateDir, params.repoRoot);
|
|
18599
19000
|
if (projectedBytes > params.config.maxBytes) throw new Error(RECOVERY_CHECKPOINT_QUOTA);
|
|
18600
|
-
await
|
|
19001
|
+
await rename4(temporary, finalDir);
|
|
18601
19002
|
await fsyncPath2(checkpointsRoot(params.stateDir));
|
|
18602
19003
|
return {
|
|
18603
19004
|
checkpointId,
|
|
@@ -18635,7 +19036,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18635
19036
|
} catch {
|
|
18636
19037
|
try {
|
|
18637
19038
|
const raw = JSON.parse(
|
|
18638
|
-
await
|
|
19039
|
+
await readFile14(path56.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
|
|
18639
19040
|
);
|
|
18640
19041
|
rootFromArtifact = typeof raw.repoRoot === "string" && raw.repoRoot ? raw.repoRoot : void 0;
|
|
18641
19042
|
} catch {
|
|
@@ -18669,7 +19070,7 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18669
19070
|
} catch {
|
|
18670
19071
|
try {
|
|
18671
19072
|
const manifest = JSON.parse(
|
|
18672
|
-
await
|
|
19073
|
+
await readFile14(path56.join(checkpointDir(stateDir, id), "manifest.json"), "utf8")
|
|
18673
19074
|
);
|
|
18674
19075
|
if (manifest.checkpointId !== id || ![1, 2].includes(manifest.version)) continue;
|
|
18675
19076
|
if (repoRoot && canonicalPath(manifest.repoRoot) !== canonicalPath(repoRoot)) continue;
|
|
@@ -18694,12 +19095,12 @@ async function listRecoveryCheckpoints(stateDir, repoRoot) {
|
|
|
18694
19095
|
async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
|
|
18695
19096
|
const root = checkpointsRoot(stateDir);
|
|
18696
19097
|
if (!repoRoot) return directorySize(root);
|
|
18697
|
-
if (!
|
|
19098
|
+
if (!existsSync16(root)) return 0;
|
|
18698
19099
|
const expected = canonicalPath(repoRoot);
|
|
18699
19100
|
let total = 0;
|
|
18700
19101
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
18701
19102
|
if (!entry.isDirectory()) continue;
|
|
18702
|
-
const entryPath =
|
|
19103
|
+
const entryPath = path56.join(root, entry.name);
|
|
18703
19104
|
if (/^cp_[a-f0-9]{24}$/.test(entry.name)) {
|
|
18704
19105
|
if (await artifactRepoRoot(stateDir, entry.name) === expected) {
|
|
18705
19106
|
total += await directorySize(entryPath);
|
|
@@ -18708,7 +19109,7 @@ async function recoveryCheckpointStorageBytes(stateDir, repoRoot) {
|
|
|
18708
19109
|
}
|
|
18709
19110
|
if (/^\.tmp-cp_[a-f0-9]{24}$/.test(entry.name)) {
|
|
18710
19111
|
try {
|
|
18711
|
-
const owner = JSON.parse(await
|
|
19112
|
+
const owner = JSON.parse(await readFile14(path56.join(entryPath, "owner.json"), "utf8"));
|
|
18712
19113
|
if (typeof owner.repoRoot === "string" && canonicalPath(owner.repoRoot) === expected) {
|
|
18713
19114
|
total += await directorySize(entryPath);
|
|
18714
19115
|
}
|
|
@@ -18750,16 +19151,16 @@ function recoveryFailClosedResult(predicted, reason, signals = []) {
|
|
|
18750
19151
|
init_scrub();
|
|
18751
19152
|
|
|
18752
19153
|
// src/core/transactional/file-checkpoint-backend.ts
|
|
18753
|
-
import { cp, lstat as lstat11, mkdir as
|
|
19154
|
+
import { cp, lstat as lstat11, mkdir as mkdir14, mkdtemp as mkdtemp5, readdir as readdir6, rm as rm9, writeFile as writeFile10 } from "node:fs/promises";
|
|
18754
19155
|
import os5 from "node:os";
|
|
18755
|
-
import
|
|
19156
|
+
import path60 from "node:path";
|
|
18756
19157
|
|
|
18757
19158
|
// src/core/transactional/file-checkpoint-git.ts
|
|
18758
19159
|
init_path_utils();
|
|
18759
19160
|
import { spawn as spawn8 } from "node:child_process";
|
|
18760
19161
|
import { createHash as createHash13 } from "node:crypto";
|
|
18761
|
-
import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as
|
|
18762
|
-
import
|
|
19162
|
+
import { copyFile as copyFile3, lstat as lstat9, readdir as readdir4, readFile as readFile15 } from "node:fs/promises";
|
|
19163
|
+
import path57 from "node:path";
|
|
18763
19164
|
var FILE_CHECKPOINT_GIT_METADATA_CHANGED = "file_checkpoint_git_metadata_changed";
|
|
18764
19165
|
var FILE_CHECKPOINT_SOURCE_CHANGED = "file_checkpoint_source_changed";
|
|
18765
19166
|
var FILE_CHECKPOINT_CWD_OUTSIDE_ROOT = "file_checkpoint_cwd_outside_root";
|
|
@@ -18793,7 +19194,7 @@ function rethrowStableFileCheckpointError(error) {
|
|
|
18793
19194
|
}
|
|
18794
19195
|
async function rootGitMetadataPresent(repoRoot) {
|
|
18795
19196
|
try {
|
|
18796
|
-
await lstat9(
|
|
19197
|
+
await lstat9(path57.join(repoRoot, ".git"));
|
|
18797
19198
|
return true;
|
|
18798
19199
|
} catch {
|
|
18799
19200
|
return false;
|
|
@@ -18842,10 +19243,10 @@ function execGit2(repoRoot, args) {
|
|
|
18842
19243
|
}
|
|
18843
19244
|
async function resolveGitPath(repoRoot, gitPath) {
|
|
18844
19245
|
const trimmed = gitPath.trim();
|
|
18845
|
-
if (
|
|
19246
|
+
if (path57.isAbsolute(trimmed)) {
|
|
18846
19247
|
return trimmed;
|
|
18847
19248
|
}
|
|
18848
|
-
return
|
|
19249
|
+
return path57.join(repoRoot, trimmed);
|
|
18849
19250
|
}
|
|
18850
19251
|
async function cloneBareWorktreeCopy(sourceRoot, destinationRoot) {
|
|
18851
19252
|
await execGit2(sourceRoot, [
|
|
@@ -18887,7 +19288,7 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
|
|
|
18887
19288
|
destinationRoot,
|
|
18888
19289
|
await execGit2(destinationRoot, ["rev-parse", "--git-dir"])
|
|
18889
19290
|
);
|
|
18890
|
-
const destinationShared =
|
|
19291
|
+
const destinationShared = path57.join(destinationGitDir, path57.basename(sourceShared));
|
|
18891
19292
|
try {
|
|
18892
19293
|
await copyFile3(sourceShared, destinationShared);
|
|
18893
19294
|
} catch (error) {
|
|
@@ -18896,14 +19297,14 @@ async function copyGitIndexState(sourceRoot, destinationRoot) {
|
|
|
18896
19297
|
}
|
|
18897
19298
|
async function readGitFile(gitDir, relativePath) {
|
|
18898
19299
|
try {
|
|
18899
|
-
return await
|
|
19300
|
+
return await readFile15(path57.join(gitDir, relativePath));
|
|
18900
19301
|
} catch {
|
|
18901
19302
|
return null;
|
|
18902
19303
|
}
|
|
18903
19304
|
}
|
|
18904
19305
|
async function readAbsoluteGitFile(absolutePath) {
|
|
18905
19306
|
try {
|
|
18906
|
-
return await
|
|
19307
|
+
return await readFile15(absolutePath);
|
|
18907
19308
|
} catch {
|
|
18908
19309
|
return null;
|
|
18909
19310
|
}
|
|
@@ -18920,8 +19321,8 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
|
|
|
18920
19321
|
repoRoot,
|
|
18921
19322
|
await execGit2(repoRoot, ["rev-parse", "--git-path", gitPath])
|
|
18922
19323
|
);
|
|
18923
|
-
const relative =
|
|
18924
|
-
const content = typeof relative === "string" && !relative.startsWith("..") && !
|
|
19324
|
+
const relative = path57.resolve(resolved).startsWith(path57.resolve(gitDir)) ? path57.relative(gitDir, resolved) : resolved;
|
|
19325
|
+
const content = typeof relative === "string" && !relative.startsWith("..") && !path57.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
|
|
18925
19326
|
if (content !== null) {
|
|
18926
19327
|
hashGitFileContent(hash, gitPath, content);
|
|
18927
19328
|
}
|
|
@@ -18931,13 +19332,13 @@ async function hashResolvedGitPath(repoRoot, gitDir, gitPath, hash) {
|
|
|
18931
19332
|
async function hashGitTree(gitDir, relativeDir, hash) {
|
|
18932
19333
|
let names;
|
|
18933
19334
|
try {
|
|
18934
|
-
names = await readdir4(
|
|
19335
|
+
names = await readdir4(path57.join(gitDir, relativeDir));
|
|
18935
19336
|
} catch {
|
|
18936
19337
|
return;
|
|
18937
19338
|
}
|
|
18938
19339
|
for (const name of names.sort()) {
|
|
18939
|
-
const relativePath = relativeDir ?
|
|
18940
|
-
const absolutePath =
|
|
19340
|
+
const relativePath = relativeDir ? path57.join(relativeDir, name) : name;
|
|
19341
|
+
const absolutePath = path57.join(gitDir, relativePath);
|
|
18941
19342
|
let childNames = null;
|
|
18942
19343
|
try {
|
|
18943
19344
|
childNames = await readdir4(absolutePath);
|
|
@@ -18959,7 +19360,7 @@ async function hashGitTree(gitDir, relativeDir, hash) {
|
|
|
18959
19360
|
}
|
|
18960
19361
|
async function computeGitMetadataFingerprint(repoRoot) {
|
|
18961
19362
|
const gitDirRel = (await execGit2(repoRoot, ["rev-parse", "--git-dir"])).trim();
|
|
18962
|
-
const gitDir =
|
|
19363
|
+
const gitDir = path57.isAbsolute(gitDirRel) ? gitDirRel : path57.join(repoRoot, gitDirRel);
|
|
18963
19364
|
const hash = createHash13("sha256");
|
|
18964
19365
|
for (const file of [
|
|
18965
19366
|
"HEAD",
|
|
@@ -18982,8 +19383,8 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18982
19383
|
const sharedIndex = (await execGit2(repoRoot, ["rev-parse", "--shared-index-path"])).trim();
|
|
18983
19384
|
if (sharedIndex) {
|
|
18984
19385
|
const resolved = await resolveGitPath(repoRoot, sharedIndex);
|
|
18985
|
-
const relative =
|
|
18986
|
-
const content = relative && !relative.startsWith("..") && !
|
|
19386
|
+
const relative = path57.relative(gitDir, resolved);
|
|
19387
|
+
const content = relative && !relative.startsWith("..") && !path57.isAbsolute(relative) ? await readGitFile(gitDir, relative) : await readAbsoluteGitFile(resolved);
|
|
18987
19388
|
if (content !== null) {
|
|
18988
19389
|
hashGitFileContent(hash, "shared-index", content);
|
|
18989
19390
|
}
|
|
@@ -18994,7 +19395,7 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
18994
19395
|
await hashResolvedGitPath(repoRoot, gitDir, gitPath, hash);
|
|
18995
19396
|
}
|
|
18996
19397
|
try {
|
|
18997
|
-
const rootGitPath =
|
|
19398
|
+
const rootGitPath = path57.join(repoRoot, ".git");
|
|
18998
19399
|
const rootGitInfo = await lstat9(rootGitPath);
|
|
18999
19400
|
if (rootGitInfo.isFile()) {
|
|
19000
19401
|
const content = await readAbsoluteGitFile(rootGitPath);
|
|
@@ -19010,14 +19411,14 @@ async function computeGitMetadataFingerprint(repoRoot) {
|
|
|
19010
19411
|
function resolveExecutionCwdRelative(resourceRoot, cwd) {
|
|
19011
19412
|
const resolvedCwd = canonicalPath(cwd);
|
|
19012
19413
|
const resourceCanonical = canonicalPath(resourceRoot);
|
|
19013
|
-
const relative =
|
|
19414
|
+
const relative = path57.relative(resourceCanonical, resolvedCwd);
|
|
19014
19415
|
if (relative === "" || relative === ".") {
|
|
19015
19416
|
return "";
|
|
19016
19417
|
}
|
|
19017
|
-
if (relative.startsWith("..") ||
|
|
19418
|
+
if (relative.startsWith("..") || path57.isAbsolute(relative)) {
|
|
19018
19419
|
throw new Error(FILE_CHECKPOINT_CWD_OUTSIDE_ROOT);
|
|
19019
19420
|
}
|
|
19020
|
-
return relative.split(
|
|
19421
|
+
return relative.split(path57.sep).join("/");
|
|
19021
19422
|
}
|
|
19022
19423
|
|
|
19023
19424
|
// src/core/transactional/file-checkpoint-isolation.ts
|
|
@@ -19038,8 +19439,8 @@ function fileCheckpointIsolationReason(context) {
|
|
|
19038
19439
|
}
|
|
19039
19440
|
|
|
19040
19441
|
// src/core/transactional/file-checkpoint-staging.ts
|
|
19041
|
-
import { readdir as readdir5, readFile as
|
|
19042
|
-
import
|
|
19442
|
+
import { readdir as readdir5, readFile as readFile16, rm as rm7, writeFile as writeFile8 } from "node:fs/promises";
|
|
19443
|
+
import path58 from "node:path";
|
|
19043
19444
|
function isOwnerProcessAlive(pid) {
|
|
19044
19445
|
try {
|
|
19045
19446
|
process.kill(pid, 0);
|
|
@@ -19049,12 +19450,12 @@ function isOwnerProcessAlive(pid) {
|
|
|
19049
19450
|
}
|
|
19050
19451
|
}
|
|
19051
19452
|
async function writeOwnerMarker(stagingRoot, marker) {
|
|
19052
|
-
await writeFile8(
|
|
19453
|
+
await writeFile8(path58.join(stagingRoot, "owner.json"), `${JSON.stringify(marker)}
|
|
19053
19454
|
`, "utf8");
|
|
19054
19455
|
}
|
|
19055
19456
|
async function readOwnerMarker(stagingRoot) {
|
|
19056
19457
|
try {
|
|
19057
|
-
const raw = await
|
|
19458
|
+
const raw = await readFile16(path58.join(stagingRoot, "owner.json"), "utf8");
|
|
19058
19459
|
return JSON.parse(raw.trim());
|
|
19059
19460
|
} catch {
|
|
19060
19461
|
return null;
|
|
@@ -19072,7 +19473,7 @@ async function collectDeadOwnerStaging(parentDir) {
|
|
|
19072
19473
|
if (!name.startsWith("belay-file-checkpoint-")) {
|
|
19073
19474
|
continue;
|
|
19074
19475
|
}
|
|
19075
|
-
const stagingRoot =
|
|
19476
|
+
const stagingRoot = path58.join(parentDir, name);
|
|
19076
19477
|
const marker = await readOwnerMarker(stagingRoot);
|
|
19077
19478
|
if (!marker) {
|
|
19078
19479
|
dead.push(stagingRoot);
|
|
@@ -19095,7 +19496,7 @@ import { constants as fsConstants2 } from "node:fs";
|
|
|
19095
19496
|
import {
|
|
19096
19497
|
copyFile as copyFile4,
|
|
19097
19498
|
lstat as lstat10,
|
|
19098
|
-
mkdir as
|
|
19499
|
+
mkdir as mkdir13,
|
|
19099
19500
|
mkdtemp as mkdtemp4,
|
|
19100
19501
|
readlink as readlink5,
|
|
19101
19502
|
rm as rm8,
|
|
@@ -19104,17 +19505,17 @@ import {
|
|
|
19104
19505
|
writeFile as writeFile9
|
|
19105
19506
|
} from "node:fs/promises";
|
|
19106
19507
|
import os4 from "node:os";
|
|
19107
|
-
import
|
|
19508
|
+
import path59 from "node:path";
|
|
19108
19509
|
var FILE_CHECKPOINT_COPY_FAILED = "file_checkpoint_copy_failed";
|
|
19109
19510
|
async function chmodSafe2(target, mode) {
|
|
19110
19511
|
try {
|
|
19111
|
-
const { chmod:
|
|
19112
|
-
await
|
|
19512
|
+
const { chmod: chmod5 } = await import("node:fs/promises");
|
|
19513
|
+
await chmod5(target, mode & 511);
|
|
19113
19514
|
} catch {
|
|
19114
19515
|
}
|
|
19115
19516
|
}
|
|
19116
19517
|
async function copyRegularFile(sourcePath, destinationPath, mode, strategy) {
|
|
19117
|
-
await
|
|
19518
|
+
await mkdir13(path59.dirname(destinationPath), { recursive: true });
|
|
19118
19519
|
if (strategy === "clonefile" && fsConstants2.COPYFILE_FICLONE !== void 0) {
|
|
19119
19520
|
try {
|
|
19120
19521
|
await copyFile4(sourcePath, destinationPath, fsConstants2.COPYFILE_FICLONE);
|
|
@@ -19136,11 +19537,11 @@ async function copyNode(sourceRoot, destinationRoot, relativePath, strategy) {
|
|
|
19136
19537
|
const info = await lstat10(sourcePath);
|
|
19137
19538
|
await rm8(destinationPath, { force: true, recursive: false });
|
|
19138
19539
|
if (info.isDirectory() && !info.isSymbolicLink()) {
|
|
19139
|
-
await
|
|
19540
|
+
await mkdir13(destinationPath, { recursive: true, mode: info.mode & 511 });
|
|
19140
19541
|
return strategy;
|
|
19141
19542
|
}
|
|
19142
19543
|
if (info.isSymbolicLink()) {
|
|
19143
|
-
await
|
|
19544
|
+
await mkdir13(path59.dirname(destinationPath), { recursive: true });
|
|
19144
19545
|
await symlink4(await readlink5(sourcePath), destinationPath);
|
|
19145
19546
|
return strategy;
|
|
19146
19547
|
}
|
|
@@ -19196,9 +19597,9 @@ async function mapWithConcurrency(items, concurrency, worker) {
|
|
|
19196
19597
|
async function probeFileCloneStrategy() {
|
|
19197
19598
|
let tempDir = null;
|
|
19198
19599
|
try {
|
|
19199
|
-
tempDir = await mkdtemp4(
|
|
19200
|
-
const source =
|
|
19201
|
-
const destination =
|
|
19600
|
+
tempDir = await mkdtemp4(path59.join(os4.tmpdir(), "belay-clone-probe-"));
|
|
19601
|
+
const source = path59.join(tempDir, "source.txt");
|
|
19602
|
+
const destination = path59.join(tempDir, "dest.txt");
|
|
19202
19603
|
await writeFile9(source, "probe\n");
|
|
19203
19604
|
if (fsConstants2.COPYFILE_FICLONE_FORCE !== void 0) {
|
|
19204
19605
|
try {
|
|
@@ -19351,11 +19752,11 @@ async function protectedRootState(root) {
|
|
|
19351
19752
|
return `directory:${node.hash}:${index.treeHash}`;
|
|
19352
19753
|
}
|
|
19353
19754
|
function executionProtectedRoot(resourceRoot, executionRoot, protectedRoot) {
|
|
19354
|
-
const relative =
|
|
19355
|
-
if (relative === "" || relative.startsWith("..") ||
|
|
19755
|
+
const relative = path60.relative(path60.resolve(resourceRoot), path60.resolve(protectedRoot));
|
|
19756
|
+
if (relative === "" || relative.startsWith("..") || path60.isAbsolute(relative)) {
|
|
19356
19757
|
return null;
|
|
19357
19758
|
}
|
|
19358
|
-
return
|
|
19759
|
+
return path60.join(executionRoot, relative);
|
|
19359
19760
|
}
|
|
19360
19761
|
async function captureProtectedRootStates(resourceRoot, executionRoot, protectedRoots) {
|
|
19361
19762
|
const states = /* @__PURE__ */ new Map();
|
|
@@ -19380,15 +19781,15 @@ async function directoryByteSize(root, deadlineMs) {
|
|
|
19380
19781
|
}
|
|
19381
19782
|
let total = 0;
|
|
19382
19783
|
for (const name of await readdir6(root)) {
|
|
19383
|
-
total += await directoryByteSize(
|
|
19784
|
+
total += await directoryByteSize(path60.join(root, name), deadlineMs);
|
|
19384
19785
|
}
|
|
19385
19786
|
return total;
|
|
19386
19787
|
}
|
|
19387
19788
|
async function copyGitMetadataDirectory(sourceRoot, destinationRoot) {
|
|
19388
19789
|
const gitDirRel = (await execGit2(sourceRoot, ["rev-parse", "--git-dir"])).trim();
|
|
19389
|
-
const sourceGitDir =
|
|
19390
|
-
const relativeGitDir =
|
|
19391
|
-
const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ?
|
|
19790
|
+
const sourceGitDir = path60.isAbsolute(gitDirRel) ? gitDirRel : path60.join(sourceRoot, gitDirRel);
|
|
19791
|
+
const relativeGitDir = path60.relative(path60.resolve(sourceRoot), path60.resolve(sourceGitDir));
|
|
19792
|
+
const destinationGitDir = relativeGitDir && !relativeGitDir.startsWith("..") ? path60.join(destinationRoot, relativeGitDir) : path60.join(destinationRoot, ".git");
|
|
19392
19793
|
await cp(sourceGitDir, destinationGitDir, { recursive: true, force: true });
|
|
19393
19794
|
}
|
|
19394
19795
|
async function prepareDirtyGitSnapshot(context) {
|
|
@@ -19397,7 +19798,7 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19397
19798
|
const quotas = context.fileCheckpoint;
|
|
19398
19799
|
const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
|
|
19399
19800
|
await removeDeadOwnerStaging(os5.tmpdir());
|
|
19400
|
-
const stagingRoot = await mkdtemp5(
|
|
19801
|
+
const stagingRoot = await mkdtemp5(path60.join(os5.tmpdir(), "belay-file-checkpoint-"));
|
|
19401
19802
|
await writeOwnerMarker(stagingRoot, {
|
|
19402
19803
|
version: 1,
|
|
19403
19804
|
pid: process.pid,
|
|
@@ -19405,8 +19806,8 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19405
19806
|
resourceRoot: context.repoRoot,
|
|
19406
19807
|
backend: "file_checkpoint"
|
|
19407
19808
|
});
|
|
19408
|
-
const baselineRoot =
|
|
19409
|
-
const executionRoot =
|
|
19809
|
+
const baselineRoot = path60.join(stagingRoot, "baseline");
|
|
19810
|
+
const executionRoot = path60.join(stagingRoot, "execution");
|
|
19410
19811
|
try {
|
|
19411
19812
|
resolveExecutionCwdRelative(context.repoRoot, context.cwd);
|
|
19412
19813
|
const sourceGitMetadataFingerprint = await computeGitMetadataFingerprint(context.repoRoot);
|
|
@@ -19440,7 +19841,7 @@ async function prepareDirtyGitSnapshot(context) {
|
|
|
19440
19841
|
throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
|
|
19441
19842
|
}
|
|
19442
19843
|
await writeFile10(
|
|
19443
|
-
|
|
19844
|
+
path60.join(stagingRoot, "baseline-index.json"),
|
|
19444
19845
|
`${JSON.stringify(baselineIndex)}
|
|
19445
19846
|
`,
|
|
19446
19847
|
"utf8"
|
|
@@ -19490,7 +19891,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19490
19891
|
const quotas = context.fileCheckpoint;
|
|
19491
19892
|
const deadlineMs = Date.now() + quotas.prepareTimeoutMs;
|
|
19492
19893
|
await removeDeadOwnerStaging(os5.tmpdir());
|
|
19493
|
-
const stagingRoot = await mkdtemp5(
|
|
19894
|
+
const stagingRoot = await mkdtemp5(path60.join(os5.tmpdir(), "belay-file-checkpoint-"));
|
|
19494
19895
|
await writeOwnerMarker(stagingRoot, {
|
|
19495
19896
|
version: 1,
|
|
19496
19897
|
pid: process.pid,
|
|
@@ -19498,8 +19899,8 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19498
19899
|
resourceRoot: context.repoRoot,
|
|
19499
19900
|
backend: "file_checkpoint"
|
|
19500
19901
|
});
|
|
19501
|
-
const baselineRoot =
|
|
19502
|
-
const executionRoot =
|
|
19902
|
+
const baselineRoot = path60.join(stagingRoot, "baseline");
|
|
19903
|
+
const executionRoot = path60.join(stagingRoot, "execution");
|
|
19503
19904
|
try {
|
|
19504
19905
|
resolveExecutionCwdRelative(context.repoRoot, context.cwd);
|
|
19505
19906
|
const resourceIdentity = await currentRecoveryResourceIdentity(context.repoRoot, "directory");
|
|
@@ -19508,7 +19909,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19508
19909
|
quotas,
|
|
19509
19910
|
deadlineMs
|
|
19510
19911
|
});
|
|
19511
|
-
await
|
|
19912
|
+
await mkdir14(baselineRoot, { recursive: true });
|
|
19512
19913
|
const baselineIndex = await buildFileTreeIndex({
|
|
19513
19914
|
resourceRoot: baselineRoot,
|
|
19514
19915
|
excludedRoots,
|
|
@@ -19528,7 +19929,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19528
19929
|
throw new Error(FILE_CHECKPOINT_SOURCE_CHANGED);
|
|
19529
19930
|
}
|
|
19530
19931
|
await writeFile10(
|
|
19531
|
-
|
|
19932
|
+
path60.join(stagingRoot, "baseline-index.json"),
|
|
19532
19933
|
`${JSON.stringify(baselineIndex)}
|
|
19533
19934
|
`,
|
|
19534
19935
|
"utf8"
|
|
@@ -19538,7 +19939,7 @@ async function prepareNonGitSnapshot(context) {
|
|
|
19538
19939
|
quotas,
|
|
19539
19940
|
deadlineMs
|
|
19540
19941
|
});
|
|
19541
|
-
await
|
|
19942
|
+
await mkdir14(executionRoot, { recursive: true });
|
|
19542
19943
|
const finalSourceIndex = await buildFileTreeIndex({
|
|
19543
19944
|
resourceRoot: context.repoRoot,
|
|
19544
19945
|
excludedRoots,
|
|
@@ -19881,10 +20282,10 @@ async function selectTransactionalBackend(context) {
|
|
|
19881
20282
|
}
|
|
19882
20283
|
|
|
19883
20284
|
// src/core/transactional/diff-evaluator.ts
|
|
19884
|
-
import
|
|
20285
|
+
import path61 from "node:path";
|
|
19885
20286
|
init_path_utils();
|
|
19886
20287
|
function categorizeChange(change, ctx) {
|
|
19887
|
-
const absolutePath = canonicalPath(
|
|
20288
|
+
const absolutePath = canonicalPath(path61.join(ctx.repoRoot, change.relativePath));
|
|
19888
20289
|
if (!pathWithinRoot(ctx.repoRoot, absolutePath)) {
|
|
19889
20290
|
return "repo_outside";
|
|
19890
20291
|
}
|
|
@@ -20434,17 +20835,55 @@ function formatJudgeInfrastructureDenyMessage(params) {
|
|
|
20434
20835
|
}
|
|
20435
20836
|
|
|
20436
20837
|
// src/core/notify.ts
|
|
20838
|
+
init_path_utils();
|
|
20437
20839
|
import { execFile } from "node:child_process";
|
|
20840
|
+
import path62 from "node:path";
|
|
20438
20841
|
import { promisify } from "node:util";
|
|
20439
20842
|
var execFileAsync = promisify(execFile);
|
|
20440
|
-
|
|
20441
|
-
|
|
20442
|
-
|
|
20843
|
+
var LOOPBACK_WEBHOOK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
20844
|
+
function webhookConfigIssue(url) {
|
|
20845
|
+
let parsed;
|
|
20846
|
+
try {
|
|
20847
|
+
parsed = new URL(url);
|
|
20848
|
+
} catch {
|
|
20849
|
+
return `notifications.webhookUrl is invalid: ${url}`;
|
|
20850
|
+
}
|
|
20851
|
+
if (parsed.protocol === "https:") {
|
|
20852
|
+
return null;
|
|
20853
|
+
}
|
|
20854
|
+
const normalizedHostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
20855
|
+
if (parsed.protocol === "http:" && LOOPBACK_WEBHOOK_HOSTS.has(normalizedHostname)) {
|
|
20856
|
+
return null;
|
|
20857
|
+
}
|
|
20858
|
+
return `notifications.webhookUrl must use https (http is allowed only for localhost, 127.0.0.1, or ::1): ${url}`;
|
|
20859
|
+
}
|
|
20860
|
+
function commandHookConfigIssue(commandHook, repoRoot) {
|
|
20861
|
+
if (!path62.isAbsolute(commandHook)) {
|
|
20862
|
+
return `notifications.commandHook must be an absolute path: ${commandHook}`;
|
|
20863
|
+
}
|
|
20864
|
+
if (pathWithinRoot(canonicalPath(repoRoot), canonicalPath(commandHook))) {
|
|
20865
|
+
return `notifications.commandHook must not be inside the repository: ${commandHook}`;
|
|
20866
|
+
}
|
|
20867
|
+
return null;
|
|
20868
|
+
}
|
|
20869
|
+
async function notifyDeny(config, event, deps = {
|
|
20870
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
20871
|
+
execFile: (file, args, options) => execFileAsync(file, [...args], options)
|
|
20872
|
+
}) {
|
|
20873
|
+
const payload = JSON.stringify({
|
|
20874
|
+
approvalId: event.approvalId,
|
|
20875
|
+
reason: event.reason,
|
|
20876
|
+
summary: event.summary,
|
|
20877
|
+
repoRoot: event.repoRoot,
|
|
20878
|
+
fingerprint: event.fingerprint
|
|
20879
|
+
});
|
|
20880
|
+
const webhookIssue = config.webhookUrl ? webhookConfigIssue(config.webhookUrl) : null;
|
|
20881
|
+
if (config.webhookUrl && !webhookIssue) {
|
|
20443
20882
|
try {
|
|
20444
20883
|
const controller = new AbortController();
|
|
20445
20884
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
20446
20885
|
try {
|
|
20447
|
-
await fetch(config.webhookUrl, {
|
|
20886
|
+
await deps.fetch(config.webhookUrl, {
|
|
20448
20887
|
method: "POST",
|
|
20449
20888
|
headers: { "content-type": "application/json" },
|
|
20450
20889
|
body: payload,
|
|
@@ -20456,17 +20895,16 @@ async function notifyDeny(config, event) {
|
|
|
20456
20895
|
} catch {
|
|
20457
20896
|
}
|
|
20458
20897
|
}
|
|
20459
|
-
|
|
20898
|
+
const commandHookIssue = config.commandHook ? commandHookConfigIssue(config.commandHook, event.repoRoot) : null;
|
|
20899
|
+
if (config.commandHook && !commandHookIssue) {
|
|
20460
20900
|
try {
|
|
20461
|
-
await
|
|
20901
|
+
await deps.execFile(config.commandHook, [], {
|
|
20462
20902
|
env: {
|
|
20463
|
-
...process.env,
|
|
20464
20903
|
BELAY_APPROVAL_ID: event.approvalId,
|
|
20465
20904
|
BELAY_REASON: event.reason,
|
|
20466
20905
|
BELAY_SUMMARY: event.summary,
|
|
20467
20906
|
BELAY_REPO_ROOT: event.repoRoot,
|
|
20468
|
-
BELAY_FINGERPRINT: event.fingerprint
|
|
20469
|
-
BELAY_APPROVAL_TOKEN: event.approvalToken ?? ""
|
|
20907
|
+
BELAY_FINGERPRINT: event.fingerprint
|
|
20470
20908
|
}
|
|
20471
20909
|
});
|
|
20472
20910
|
} catch {
|
|
@@ -20476,9 +20914,10 @@ async function notifyDeny(config, event) {
|
|
|
20476
20914
|
|
|
20477
20915
|
// src/adapters/shared/gate-runtime.ts
|
|
20478
20916
|
init_path_utils();
|
|
20917
|
+
init_repo_config_trust();
|
|
20479
20918
|
|
|
20480
20919
|
// src/adapters/layouts/protected-paths.ts
|
|
20481
|
-
import
|
|
20920
|
+
import path63 from "node:path";
|
|
20482
20921
|
function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
|
|
20483
20922
|
const roots = [
|
|
20484
20923
|
layout.configPath(repoRoot),
|
|
@@ -20490,7 +20929,7 @@ function protectedArtifactRoots(layout, repoRoot, controlPlaneDir) {
|
|
|
20490
20929
|
if (controlPlaneDir) {
|
|
20491
20930
|
roots.push(controlPlaneDir);
|
|
20492
20931
|
}
|
|
20493
|
-
return roots.map((entry) =>
|
|
20932
|
+
return roots.map((entry) => path63.resolve(entry));
|
|
20494
20933
|
}
|
|
20495
20934
|
|
|
20496
20935
|
// src/adapters/shared/gate-runtime.ts
|
|
@@ -20531,7 +20970,7 @@ async function appendReplayAuditSafely(ctx, deps, event) {
|
|
|
20531
20970
|
}
|
|
20532
20971
|
async function loadJsonFile(filePath, fallback) {
|
|
20533
20972
|
try {
|
|
20534
|
-
const raw = await
|
|
20973
|
+
const raw = await readFile17(filePath, "utf8");
|
|
20535
20974
|
return JSON.parse(raw);
|
|
20536
20975
|
} catch {
|
|
20537
20976
|
return fallback;
|
|
@@ -20540,11 +20979,18 @@ async function loadJsonFile(filePath, fallback) {
|
|
|
20540
20979
|
function createDefaultGateRuntimeDeps() {
|
|
20541
20980
|
return {
|
|
20542
20981
|
async readConfig(configPath) {
|
|
20543
|
-
|
|
20982
|
+
try {
|
|
20983
|
+
return JSON.parse(await readFile17(configPath, "utf8"));
|
|
20984
|
+
} catch (error) {
|
|
20985
|
+
if (error.code === "ENOENT") {
|
|
20986
|
+
return {};
|
|
20987
|
+
}
|
|
20988
|
+
throw error;
|
|
20989
|
+
}
|
|
20544
20990
|
},
|
|
20545
20991
|
async appendAudit(ctx, event) {
|
|
20546
|
-
const auditPath =
|
|
20547
|
-
await
|
|
20992
|
+
const auditPath = path64.join(ctx.repoRoot, ctx.config.audit.logPath);
|
|
20993
|
+
await mkdir15(path64.dirname(auditPath), { recursive: true });
|
|
20548
20994
|
const provenance = auditProvenance(ctx.config);
|
|
20549
20995
|
const record = {
|
|
20550
20996
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -20577,7 +21023,7 @@ function createDefaultGateRuntimeDeps() {
|
|
|
20577
21023
|
};
|
|
20578
21024
|
},
|
|
20579
21025
|
async writeApprovals(filePath, state) {
|
|
20580
|
-
await
|
|
21026
|
+
await mkdir15(path64.dirname(filePath), { recursive: true });
|
|
20581
21027
|
await writeFile11(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}
|
|
20582
21028
|
`, "utf8");
|
|
20583
21029
|
},
|
|
@@ -20598,10 +21044,11 @@ function createDefaultGateRuntimeDeps() {
|
|
|
20598
21044
|
}
|
|
20599
21045
|
async function resolveGateConfig(ctx, deps) {
|
|
20600
21046
|
const loaded = await deps.readConfig(ctx.configPath);
|
|
21047
|
+
await assertRepoConfigTrusted(ctx.repoRoot, ctx.layout.name, loaded);
|
|
20601
21048
|
let teamConfig = null;
|
|
20602
21049
|
const teamPath = teamConfigPath();
|
|
20603
|
-
if (
|
|
20604
|
-
teamConfig = JSON.parse(await
|
|
21050
|
+
if (existsSync17(teamPath)) {
|
|
21051
|
+
teamConfig = JSON.parse(await readFile17(teamPath, "utf8"));
|
|
20605
21052
|
}
|
|
20606
21053
|
return resolveLayeredConfig({
|
|
20607
21054
|
repoConfig: loaded,
|
|
@@ -20739,7 +21186,7 @@ function deriveWorkspaceRootScopeHint(params) {
|
|
|
20739
21186
|
if (!targetPath) {
|
|
20740
21187
|
return void 0;
|
|
20741
21188
|
}
|
|
20742
|
-
const candidateRoot = canonicalPath(
|
|
21189
|
+
const candidateRoot = canonicalPath(path64.dirname(targetPath));
|
|
20743
21190
|
const validation = validateTrustedWorkspaceRootCandidate({
|
|
20744
21191
|
candidatePath: candidateRoot,
|
|
20745
21192
|
repoRoot: action.repoRoot,
|
|
@@ -21315,7 +21762,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21315
21762
|
event: auditEvent,
|
|
21316
21763
|
sourceEvent,
|
|
21317
21764
|
kind,
|
|
21318
|
-
repoLabel:
|
|
21765
|
+
repoLabel: path64.basename(ctx.repoRoot),
|
|
21319
21766
|
...auditExtras.toolInvocationCorrelationId ? { toolInvocationCorrelationId: auditExtras.toolInvocationCorrelationId } : {},
|
|
21320
21767
|
fingerprint: result.fingerprint,
|
|
21321
21768
|
summary: result.normalizedCommand ?? result.summary ?? "",
|
|
@@ -21347,21 +21794,6 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21347
21794
|
if (created) {
|
|
21348
21795
|
await recordGateApprovalAsk(stateDir, result.reason, false);
|
|
21349
21796
|
}
|
|
21350
|
-
let approvalToken;
|
|
21351
|
-
try {
|
|
21352
|
-
approvalToken = await issueApprovalToken(
|
|
21353
|
-
{
|
|
21354
|
-
approvalId: approval.approvalId,
|
|
21355
|
-
fingerprint: approval.fingerprint,
|
|
21356
|
-
repoRoot: approval.repoRoot,
|
|
21357
|
-
issuedAt: approval.createdAt,
|
|
21358
|
-
expiresAt: approval.expiresAt
|
|
21359
|
-
},
|
|
21360
|
-
configuredControlPlaneDir(ctx.config)
|
|
21361
|
-
);
|
|
21362
|
-
} catch {
|
|
21363
|
-
approvalToken = void 0;
|
|
21364
|
-
}
|
|
21365
21797
|
const denialReason = failure?.reason ?? result.reason;
|
|
21366
21798
|
if (ctx.config.notifications.webhookUrl || ctx.config.notifications.commandHook) {
|
|
21367
21799
|
await notifyDeny(ctx.config.notifications, {
|
|
@@ -21369,8 +21801,7 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
21369
21801
|
reason: denialReason,
|
|
21370
21802
|
summary: result.normalizedCommand ?? result.summary ?? "",
|
|
21371
21803
|
repoRoot: ctx.repoRoot,
|
|
21372
|
-
fingerprint: result.fingerprint
|
|
21373
|
-
approvalToken
|
|
21804
|
+
fingerprint: result.fingerprint
|
|
21374
21805
|
});
|
|
21375
21806
|
}
|
|
21376
21807
|
const failureAuditFields = typeof failure?.auditFields === "function" ? failure.auditFields(approval) : failure?.auditFields;
|
|
@@ -21762,38 +22193,38 @@ async function appendObservedAudit(ctx, deps, eventName, payload) {
|
|
|
21762
22193
|
}
|
|
21763
22194
|
|
|
21764
22195
|
// src/adapters/shared/repo-root.ts
|
|
21765
|
-
import { existsSync as
|
|
21766
|
-
import
|
|
22196
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
22197
|
+
import path65 from "node:path";
|
|
21767
22198
|
function belayConfigPath(current, adapterName) {
|
|
21768
22199
|
if (adapterName === "cursor") {
|
|
21769
|
-
return
|
|
22200
|
+
return path65.join(current, ".cursor", "belay.config.json");
|
|
21770
22201
|
}
|
|
21771
22202
|
if (adapterName === "claude") {
|
|
21772
|
-
return
|
|
22203
|
+
return path65.join(current, ".claude", "belay.config.json");
|
|
21773
22204
|
}
|
|
21774
|
-
return
|
|
22205
|
+
return path65.join(current, ".codex", "belay.config.json");
|
|
21775
22206
|
}
|
|
21776
22207
|
function markerMatches(current, marker, layout) {
|
|
21777
|
-
const markerPath =
|
|
21778
|
-
if (!
|
|
22208
|
+
const markerPath = path65.join(current, marker);
|
|
22209
|
+
if (!existsSync18(markerPath)) {
|
|
21779
22210
|
return false;
|
|
21780
22211
|
}
|
|
21781
22212
|
if (marker === ".cursor" || marker === ".claude" || marker === ".codex") {
|
|
21782
|
-
return
|
|
22213
|
+
return existsSync18(belayConfigPath(current, layout.name));
|
|
21783
22214
|
}
|
|
21784
22215
|
return true;
|
|
21785
22216
|
}
|
|
21786
22217
|
function findRepoRoot(startPath, layout) {
|
|
21787
|
-
let current =
|
|
22218
|
+
let current = path65.resolve(startPath);
|
|
21788
22219
|
while (true) {
|
|
21789
22220
|
for (const marker of layout.repoRootMarkers) {
|
|
21790
22221
|
if (markerMatches(current, marker, layout)) {
|
|
21791
22222
|
return current;
|
|
21792
22223
|
}
|
|
21793
22224
|
}
|
|
21794
|
-
const parent =
|
|
22225
|
+
const parent = path65.dirname(current);
|
|
21795
22226
|
if (parent === current) {
|
|
21796
|
-
return
|
|
22227
|
+
return path65.resolve(startPath);
|
|
21797
22228
|
}
|
|
21798
22229
|
current = parent;
|
|
21799
22230
|
}
|
|
@@ -21801,7 +22232,7 @@ function findRepoRoot(startPath, layout) {
|
|
|
21801
22232
|
|
|
21802
22233
|
// src/adapters/cursor/cwd-resolution.ts
|
|
21803
22234
|
import os6 from "node:os";
|
|
21804
|
-
import
|
|
22235
|
+
import path66 from "node:path";
|
|
21805
22236
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21806
22237
|
function nonEmptyPathString(value) {
|
|
21807
22238
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -21813,36 +22244,36 @@ function resolveCursorActionCwdDetails(payload, fallback = process.cwd()) {
|
|
|
21813
22244
|
const workspaceRoot = Array.isArray(payload.workspace_roots) ? payload.workspace_roots.map(nonEmptyPathString).find(Boolean) : void 0;
|
|
21814
22245
|
if (toolInputWorkingDirectory) {
|
|
21815
22246
|
return {
|
|
21816
|
-
cwd:
|
|
22247
|
+
cwd: path66.resolve(toolInputWorkingDirectory),
|
|
21817
22248
|
source: "tool_input.working_directory",
|
|
21818
22249
|
fromPayload: true
|
|
21819
22250
|
};
|
|
21820
22251
|
}
|
|
21821
22252
|
if (topLevelCwd) {
|
|
21822
22253
|
return {
|
|
21823
|
-
cwd:
|
|
22254
|
+
cwd: path66.resolve(topLevelCwd),
|
|
21824
22255
|
source: "cwd",
|
|
21825
22256
|
fromPayload: true
|
|
21826
22257
|
};
|
|
21827
22258
|
}
|
|
21828
22259
|
if (workspaceRoot) {
|
|
21829
22260
|
return {
|
|
21830
|
-
cwd:
|
|
22261
|
+
cwd: path66.resolve(workspaceRoot),
|
|
21831
22262
|
source: "workspace_roots",
|
|
21832
22263
|
fromPayload: true
|
|
21833
22264
|
};
|
|
21834
22265
|
}
|
|
21835
22266
|
return {
|
|
21836
|
-
cwd:
|
|
22267
|
+
cwd: path66.resolve(fallback),
|
|
21837
22268
|
source: "fallback",
|
|
21838
22269
|
fromPayload: false
|
|
21839
22270
|
};
|
|
21840
22271
|
}
|
|
21841
22272
|
function isGlobalCursorHookRuntime() {
|
|
21842
22273
|
try {
|
|
21843
|
-
const runtimePath =
|
|
21844
|
-
const globalRuntimeDir =
|
|
21845
|
-
return runtimePath === globalRuntimeDir || runtimePath.startsWith(`${globalRuntimeDir}${
|
|
22274
|
+
const runtimePath = path66.resolve(fileURLToPath2(import.meta.url));
|
|
22275
|
+
const globalRuntimeDir = path66.resolve(path66.join(os6.homedir(), ".cursor", "belay", "runtime"));
|
|
22276
|
+
return runtimePath === globalRuntimeDir || runtimePath.startsWith(`${globalRuntimeDir}${path66.sep}`);
|
|
21846
22277
|
} catch {
|
|
21847
22278
|
return false;
|
|
21848
22279
|
}
|
|
@@ -21892,7 +22323,7 @@ function resolveCursorToolActionCwdDetails(payload, fallback, eventName, toolNam
|
|
|
21892
22323
|
function collectCandidateRepoRoots(payload, resolution) {
|
|
21893
22324
|
const roots = [];
|
|
21894
22325
|
const add = (value) => {
|
|
21895
|
-
const resolved =
|
|
22326
|
+
const resolved = path67.resolve(value);
|
|
21896
22327
|
if (!roots.includes(resolved)) {
|
|
21897
22328
|
roots.push(resolved);
|
|
21898
22329
|
}
|
|
@@ -21954,6 +22385,9 @@ function isSubagentEvent(payload, eventName) {
|
|
|
21954
22385
|
function isFileMutationTool(toolName) {
|
|
21955
22386
|
return toolName === "Write" || toolName === "StrReplace" || toolName === "Delete";
|
|
21956
22387
|
}
|
|
22388
|
+
function isCursorPreToolUseEvent(eventName) {
|
|
22389
|
+
return eventName === "preToolUse" || eventName === "PreToolUse";
|
|
22390
|
+
}
|
|
21957
22391
|
async function handleBeforeSubmitPromptHook(payload) {
|
|
21958
22392
|
try {
|
|
21959
22393
|
const prompt = String(payload.prompt ?? "");
|
|
@@ -21978,7 +22412,13 @@ async function handleBeforeSubmitPromptHook(payload) {
|
|
|
21978
22412
|
...result.user_message ? { user_message: result.user_message } : {},
|
|
21979
22413
|
...result.replay ? { replay: result.replay } : {}
|
|
21980
22414
|
};
|
|
21981
|
-
} catch {
|
|
22415
|
+
} catch (error) {
|
|
22416
|
+
if (isRepoConfigTrustError(error)) {
|
|
22417
|
+
return {
|
|
22418
|
+
continue: false,
|
|
22419
|
+
user_message: error.message
|
|
22420
|
+
};
|
|
22421
|
+
}
|
|
21982
22422
|
return {
|
|
21983
22423
|
continue: false,
|
|
21984
22424
|
user_message: "belay failed while processing approval state. Run belay doctor, then retry."
|
|
@@ -22009,7 +22449,13 @@ async function handleShellGateHook(payload) {
|
|
|
22009
22449
|
sourceEvent: "beforeShellExecution"
|
|
22010
22450
|
});
|
|
22011
22451
|
return gateVerdictToCursorResponse(verdict2);
|
|
22012
|
-
} catch {
|
|
22452
|
+
} catch (error) {
|
|
22453
|
+
if (isRepoConfigTrustError(error)) {
|
|
22454
|
+
return {
|
|
22455
|
+
permission: "deny",
|
|
22456
|
+
user_message: error.message
|
|
22457
|
+
};
|
|
22458
|
+
}
|
|
22013
22459
|
return {
|
|
22014
22460
|
permission: "deny",
|
|
22015
22461
|
user_message: "belay failed while classifying this shell command. Run belay doctor, then retry."
|
|
@@ -22022,6 +22468,9 @@ async function runShellGateHook() {
|
|
|
22022
22468
|
async function handleToolGateHook(eventName, payload) {
|
|
22023
22469
|
try {
|
|
22024
22470
|
const toolName = String(payload.tool_name ?? "");
|
|
22471
|
+
if (isCursorPreToolUseEvent(eventName) && toolName === "Shell") {
|
|
22472
|
+
return { permission: "allow" };
|
|
22473
|
+
}
|
|
22025
22474
|
const resolution = resolveCursorToolActionCwdDetails(
|
|
22026
22475
|
payload,
|
|
22027
22476
|
process2.cwd(),
|
|
@@ -22046,9 +22495,9 @@ async function handleToolGateHook(eventName, payload) {
|
|
|
22046
22495
|
});
|
|
22047
22496
|
return gateVerdictToCursorResponse(verdict2);
|
|
22048
22497
|
}
|
|
22049
|
-
if (toolName
|
|
22498
|
+
if (isFileMutationTool(toolName)) {
|
|
22050
22499
|
const verdict2 = await evaluateGatedAction(ctx, deps, {
|
|
22051
|
-
kind: "
|
|
22500
|
+
kind: "tool",
|
|
22052
22501
|
cwd,
|
|
22053
22502
|
payload,
|
|
22054
22503
|
toolName,
|
|
@@ -22056,27 +22505,33 @@ async function handleToolGateHook(eventName, payload) {
|
|
|
22056
22505
|
});
|
|
22057
22506
|
return gateVerdictToCursorResponse(verdict2);
|
|
22058
22507
|
}
|
|
22059
|
-
if (
|
|
22508
|
+
if (payload.tool_name === "Task") {
|
|
22060
22509
|
const verdict2 = await evaluateGatedAction(ctx, deps, {
|
|
22061
|
-
kind: "
|
|
22510
|
+
kind: "subagent",
|
|
22062
22511
|
cwd,
|
|
22063
22512
|
payload,
|
|
22064
|
-
toolName,
|
|
22065
22513
|
sourceEvent: eventName
|
|
22066
22514
|
});
|
|
22067
22515
|
return gateVerdictToCursorResponse(verdict2);
|
|
22068
22516
|
}
|
|
22069
|
-
if (
|
|
22517
|
+
if (isCursorPreToolUseEvent(eventName)) {
|
|
22070
22518
|
const verdict2 = await evaluateGatedAction(ctx, deps, {
|
|
22071
|
-
kind: "
|
|
22519
|
+
kind: "tool",
|
|
22072
22520
|
cwd,
|
|
22073
22521
|
payload,
|
|
22522
|
+
toolName,
|
|
22074
22523
|
sourceEvent: eventName
|
|
22075
22524
|
});
|
|
22076
22525
|
return gateVerdictToCursorResponse(verdict2);
|
|
22077
22526
|
}
|
|
22078
22527
|
return { permission: "allow" };
|
|
22079
|
-
} catch {
|
|
22528
|
+
} catch (error) {
|
|
22529
|
+
if (isRepoConfigTrustError(error)) {
|
|
22530
|
+
return {
|
|
22531
|
+
permission: "deny",
|
|
22532
|
+
user_message: error.message
|
|
22533
|
+
};
|
|
22534
|
+
}
|
|
22080
22535
|
return {
|
|
22081
22536
|
permission: "deny",
|
|
22082
22537
|
user_message: "belay failed while classifying this tool action. Run belay doctor, then retry."
|