local-commander-agent 0.3.3 → 0.3.4
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 +78 -22
- package/bin/localcommander.mjs +41 -11
- package/package.json +7 -2
- package/runtime/JobHost.exe +0 -0
- package/runtime/LocalCommanderAgent.exe +0 -0
- package/runtime/OwnerBridge.exe +0 -0
- package/runtime/agent.mjs +545 -312
package/runtime/agent.mjs
CHANGED
|
@@ -19,8 +19,8 @@ var __commonJS = (cb, mod) => function __require2() {
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
var __export = (target, all) => {
|
|
22
|
-
for (var
|
|
23
|
-
__defProp(target,
|
|
22
|
+
for (var name2 in all)
|
|
23
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
24
24
|
};
|
|
25
25
|
var __copyProps = (to, from, except, desc) => {
|
|
26
26
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -2121,9 +2121,9 @@ var require_extension = __commonJS({
|
|
|
2121
2121
|
"node_modules/ws/lib/extension.js"(exports, module) {
|
|
2122
2122
|
"use strict";
|
|
2123
2123
|
var { tokenChars } = require_validation();
|
|
2124
|
-
function push(dest,
|
|
2125
|
-
if (dest[
|
|
2126
|
-
else dest[
|
|
2124
|
+
function push(dest, name2, elem) {
|
|
2125
|
+
if (dest[name2] === void 0) dest[name2] = [elem];
|
|
2126
|
+
else dest[name2].push(elem);
|
|
2127
2127
|
}
|
|
2128
2128
|
function parse3(header) {
|
|
2129
2129
|
const offers = /* @__PURE__ */ Object.create(null);
|
|
@@ -2149,12 +2149,12 @@ var require_extension = __commonJS({
|
|
|
2149
2149
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
2150
2150
|
}
|
|
2151
2151
|
if (end === -1) end = i;
|
|
2152
|
-
const
|
|
2152
|
+
const name2 = header.slice(start, end);
|
|
2153
2153
|
if (code === 44) {
|
|
2154
|
-
push(offers,
|
|
2154
|
+
push(offers, name2, params);
|
|
2155
2155
|
params = /* @__PURE__ */ Object.create(null);
|
|
2156
2156
|
} else {
|
|
2157
|
-
extensionName =
|
|
2157
|
+
extensionName = name2;
|
|
2158
2158
|
}
|
|
2159
2159
|
start = end = -1;
|
|
2160
2160
|
} else {
|
|
@@ -2278,7 +2278,7 @@ var require_websocket = __commonJS({
|
|
|
2278
2278
|
var http = __require("http");
|
|
2279
2279
|
var net = __require("net");
|
|
2280
2280
|
var tls = __require("tls");
|
|
2281
|
-
var { randomBytes: randomBytes4, createHash:
|
|
2281
|
+
var { randomBytes: randomBytes4, createHash: createHash10 } = __require("crypto");
|
|
2282
2282
|
var { Duplex, Readable } = __require("stream");
|
|
2283
2283
|
var { URL: URL2 } = __require("url");
|
|
2284
2284
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2959,7 +2959,7 @@ var require_websocket = __commonJS({
|
|
|
2959
2959
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2960
2960
|
return;
|
|
2961
2961
|
}
|
|
2962
|
-
const digest4 =
|
|
2962
|
+
const digest4 = createHash10("sha1").update(key + GUID).digest("base64");
|
|
2963
2963
|
if (res.headers["sec-websocket-accept"] !== digest4) {
|
|
2964
2964
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2965
2965
|
return;
|
|
@@ -3328,7 +3328,7 @@ var require_websocket_server = __commonJS({
|
|
|
3328
3328
|
var EventEmitter = __require("events");
|
|
3329
3329
|
var http = __require("http");
|
|
3330
3330
|
var { Duplex } = __require("stream");
|
|
3331
|
-
var { createHash:
|
|
3331
|
+
var { createHash: createHash10 } = __require("crypto");
|
|
3332
3332
|
var extension2 = require_extension();
|
|
3333
3333
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3334
3334
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3635,7 +3635,7 @@ var require_websocket_server = __commonJS({
|
|
|
3635
3635
|
);
|
|
3636
3636
|
}
|
|
3637
3637
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3638
|
-
const digest4 =
|
|
3638
|
+
const digest4 = createHash10("sha1").update(key + GUID).digest("base64");
|
|
3639
3639
|
const headers = [
|
|
3640
3640
|
"HTTP/1.1 101 Switching Protocols",
|
|
3641
3641
|
"Upgrade: websocket",
|
|
@@ -3723,7 +3723,7 @@ var require_websocket_server = __commonJS({
|
|
|
3723
3723
|
});
|
|
3724
3724
|
|
|
3725
3725
|
// agent/src/main.ts
|
|
3726
|
-
import
|
|
3726
|
+
import path12 from "node:path";
|
|
3727
3727
|
import os3 from "node:os";
|
|
3728
3728
|
import { mkdir as mkdir5, open as open6, readFile as readFile5, rm as rm4 } from "node:fs/promises";
|
|
3729
3729
|
import { fileURLToPath } from "node:url";
|
|
@@ -4674,10 +4674,10 @@ function mergeDefs(...defs) {
|
|
|
4674
4674
|
function cloneDef(schema) {
|
|
4675
4675
|
return mergeDefs(schema._zod.def);
|
|
4676
4676
|
}
|
|
4677
|
-
function getElementAtPath(obj,
|
|
4678
|
-
if (!
|
|
4677
|
+
function getElementAtPath(obj, path13) {
|
|
4678
|
+
if (!path13)
|
|
4679
4679
|
return obj;
|
|
4680
|
-
return
|
|
4680
|
+
return path13.reduce((acc, key) => acc?.[key], obj);
|
|
4681
4681
|
}
|
|
4682
4682
|
function promiseAllObject(promisesObj) {
|
|
4683
4683
|
const keys = Object.keys(promisesObj);
|
|
@@ -4976,12 +4976,12 @@ function merge(a, b) {
|
|
|
4976
4976
|
});
|
|
4977
4977
|
return clone(a, def);
|
|
4978
4978
|
}
|
|
4979
|
-
function partial(Class2, schema, mask,
|
|
4979
|
+
function partial(Class2, schema, mask, name2 = "partial") {
|
|
4980
4980
|
const currDef = schema._zod.def;
|
|
4981
4981
|
const checks = currDef.checks;
|
|
4982
4982
|
const hasChecks = checks && checks.length > 0;
|
|
4983
4983
|
if (hasChecks) {
|
|
4984
|
-
throw new Error(`.${
|
|
4984
|
+
throw new Error(`.${name2}() cannot be used on object schemas containing refinements`);
|
|
4985
4985
|
}
|
|
4986
4986
|
const selected = mask ? new Set(maskedKeys(schema, mask)) : void 0;
|
|
4987
4987
|
const newShape = {};
|
|
@@ -5017,11 +5017,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
5017
5017
|
}
|
|
5018
5018
|
return false;
|
|
5019
5019
|
}
|
|
5020
|
-
function prefixIssues(
|
|
5020
|
+
function prefixIssues(path13, issues) {
|
|
5021
5021
|
return issues.map((iss) => {
|
|
5022
5022
|
var _a3;
|
|
5023
5023
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
5024
|
-
iss.path.unshift(
|
|
5024
|
+
iss.path.unshift(path13);
|
|
5025
5025
|
return iss;
|
|
5026
5026
|
});
|
|
5027
5027
|
}
|
|
@@ -5309,7 +5309,7 @@ function newError(Definition) {
|
|
|
5309
5309
|
return new Definition();
|
|
5310
5310
|
}
|
|
5311
5311
|
// @__NO_SIDE_EFFECTS__
|
|
5312
|
-
function $constructor(
|
|
5312
|
+
function $constructor(name2, initializer3, proto, params) {
|
|
5313
5313
|
const zodProto = {};
|
|
5314
5314
|
function Internals(def) {
|
|
5315
5315
|
this.def = def;
|
|
@@ -5327,10 +5327,10 @@ function $constructor(name, initializer3, proto, params) {
|
|
|
5327
5327
|
} finally {
|
|
5328
5328
|
_zodDesc.value = void 0;
|
|
5329
5329
|
}
|
|
5330
|
-
} else if (inst._zod.traits.has(
|
|
5330
|
+
} else if (inst._zod.traits.has(name2)) {
|
|
5331
5331
|
return;
|
|
5332
5332
|
}
|
|
5333
|
-
inst._zod.traits.add(
|
|
5333
|
+
inst._zod.traits.add(name2);
|
|
5334
5334
|
initializer3(inst, def);
|
|
5335
5335
|
if (initialized) {
|
|
5336
5336
|
const own2 = Object.getPrototypeOf(inst);
|
|
@@ -5356,7 +5356,7 @@ function $constructor(name, initializer3, proto, params) {
|
|
|
5356
5356
|
const Parent = params?.Parent ?? Object;
|
|
5357
5357
|
class Definition extends Parent {
|
|
5358
5358
|
}
|
|
5359
|
-
Object.defineProperty(Definition, "name", { value:
|
|
5359
|
+
Object.defineProperty(Definition, "name", { value: name2 });
|
|
5360
5360
|
function _(def) {
|
|
5361
5361
|
const inst = params?.Parent ? newError(Definition) : this;
|
|
5362
5362
|
init(inst, def);
|
|
@@ -5377,10 +5377,10 @@ function $constructor(name, initializer3, proto, params) {
|
|
|
5377
5377
|
value: (inst) => {
|
|
5378
5378
|
if (params?.Parent && inst instanceof params.Parent)
|
|
5379
5379
|
return true;
|
|
5380
|
-
return inst?._zod?.traits?.has(
|
|
5380
|
+
return inst?._zod?.traits?.has(name2);
|
|
5381
5381
|
}
|
|
5382
5382
|
});
|
|
5383
|
-
Object.defineProperty(_, "name", { value:
|
|
5383
|
+
Object.defineProperty(_, "name", { value: name2 });
|
|
5384
5384
|
return _;
|
|
5385
5385
|
}
|
|
5386
5386
|
var $brand = /* @__PURE__ */ Symbol("zod_brand");
|
|
@@ -5390,8 +5390,8 @@ var $ZodAsyncError = class extends Error {
|
|
|
5390
5390
|
}
|
|
5391
5391
|
};
|
|
5392
5392
|
var $ZodEncodeError = class extends Error {
|
|
5393
|
-
constructor(
|
|
5394
|
-
super(`Encountered unidirectional transform during encode: ${
|
|
5393
|
+
constructor(name2) {
|
|
5394
|
+
super(`Encountered unidirectional transform during encode: ${name2}`);
|
|
5395
5395
|
this.name = "ZodEncodeError";
|
|
5396
5396
|
}
|
|
5397
5397
|
};
|
|
@@ -5471,16 +5471,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5471
5471
|
}
|
|
5472
5472
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
5473
5473
|
const fieldErrors = { _errors: [] };
|
|
5474
|
-
const processError = (error63,
|
|
5474
|
+
const processError = (error63, path13 = []) => {
|
|
5475
5475
|
for (const issue2 of error63.issues) {
|
|
5476
5476
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
5477
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
5477
|
+
issue2.errors.map((issues) => processError({ issues }, [...path13, ...issue2.path]));
|
|
5478
5478
|
} else if (issue2.code === "invalid_key") {
|
|
5479
|
-
processError({ issues: issue2.issues }, [...
|
|
5479
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
5480
5480
|
} else if (issue2.code === "invalid_element") {
|
|
5481
|
-
processError({ issues: issue2.issues }, [...
|
|
5481
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
5482
5482
|
} else {
|
|
5483
|
-
const fullpath = [...
|
|
5483
|
+
const fullpath = [...path13, ...issue2.path];
|
|
5484
5484
|
if (fullpath.length === 0) {
|
|
5485
5485
|
fieldErrors._errors.push(mapper(issue2));
|
|
5486
5486
|
} else {
|
|
@@ -5519,17 +5519,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5519
5519
|
}
|
|
5520
5520
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
5521
5521
|
const result2 = { errors: [] };
|
|
5522
|
-
const processError = (error63,
|
|
5522
|
+
const processError = (error63, path13 = []) => {
|
|
5523
5523
|
var _a3;
|
|
5524
5524
|
for (const issue2 of error63.issues) {
|
|
5525
5525
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
5526
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
5526
|
+
issue2.errors.map((issues) => processError({ issues }, [...path13, ...issue2.path]));
|
|
5527
5527
|
} else if (issue2.code === "invalid_key") {
|
|
5528
|
-
processError({ issues: issue2.issues }, [...
|
|
5528
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
5529
5529
|
} else if (issue2.code === "invalid_element") {
|
|
5530
|
-
processError({ issues: issue2.issues }, [...
|
|
5530
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
5531
5531
|
} else {
|
|
5532
|
-
const fullpath = [...
|
|
5532
|
+
const fullpath = [...path13, ...issue2.path];
|
|
5533
5533
|
if (fullpath.length === 0) {
|
|
5534
5534
|
result2.errors.push(mapper(issue2));
|
|
5535
5535
|
continue;
|
|
@@ -5568,8 +5568,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5568
5568
|
}
|
|
5569
5569
|
function toDotPath(_path) {
|
|
5570
5570
|
const segs = [];
|
|
5571
|
-
const
|
|
5572
|
-
for (const seg of
|
|
5571
|
+
const path13 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
5572
|
+
for (const seg of path13) {
|
|
5573
5573
|
if (typeof seg === "number")
|
|
5574
5574
|
segs.push(`[${seg}]`);
|
|
5575
5575
|
else if (typeof seg === "symbol")
|
|
@@ -16890,13 +16890,13 @@ ${code}
|
|
|
16890
16890
|
return fn;
|
|
16891
16891
|
}
|
|
16892
16892
|
function addConstant(ctx, value) {
|
|
16893
|
-
for (const [
|
|
16893
|
+
for (const [name3, v] of ctx.constants) {
|
|
16894
16894
|
if (v === value)
|
|
16895
|
-
return
|
|
16895
|
+
return name3;
|
|
16896
16896
|
}
|
|
16897
|
-
const
|
|
16898
|
-
ctx.constants.set(
|
|
16899
|
-
return
|
|
16897
|
+
const name2 = `c${ctx.constantCounter++}`;
|
|
16898
|
+
ctx.constants.set(name2, value);
|
|
16899
|
+
return name2;
|
|
16900
16900
|
}
|
|
16901
16901
|
function addUserConstant(ctx, fn) {
|
|
16902
16902
|
ctx.definite = false;
|
|
@@ -22671,13 +22671,13 @@ function resolveRef(ref, ctx) {
|
|
|
22671
22671
|
if (!ref.startsWith("#")) {
|
|
22672
22672
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
22673
22673
|
}
|
|
22674
|
-
const
|
|
22675
|
-
if (
|
|
22674
|
+
const path13 = ref.slice(1).split("/").filter(Boolean);
|
|
22675
|
+
if (path13.length === 0) {
|
|
22676
22676
|
return ctx.rootSchema;
|
|
22677
22677
|
}
|
|
22678
22678
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
22679
|
-
if (
|
|
22680
|
-
const key =
|
|
22679
|
+
if (path13[0] === defsKey) {
|
|
22680
|
+
const key = path13[1] === void 0 ? void 0 : decodeJSONPointerSegment(path13[1]);
|
|
22681
22681
|
if (!key || !ctx.defs[key]) {
|
|
22682
22682
|
throw new Error(`Reference not found: ${ref}`);
|
|
22683
22683
|
}
|
|
@@ -23643,9 +23643,9 @@ function isDisjoint(...headers) {
|
|
|
23643
23643
|
}
|
|
23644
23644
|
return true;
|
|
23645
23645
|
}
|
|
23646
|
-
function assertNotSet(value,
|
|
23646
|
+
function assertNotSet(value, name2) {
|
|
23647
23647
|
if (value !== void 0)
|
|
23648
|
-
throw new TypeError(`${
|
|
23648
|
+
throw new TypeError(`${name2} can only be called once`);
|
|
23649
23649
|
}
|
|
23650
23650
|
function decodeBase64url(value, label, ErrorClass) {
|
|
23651
23651
|
try {
|
|
@@ -23806,7 +23806,7 @@ function invalidKeyType(alg, actual, secret) {
|
|
|
23806
23806
|
const types = ["CryptoKey", "KeyObject", "JSON Web Key"];
|
|
23807
23807
|
return secret && types.push("Uint8Array"), new TypeError(message(`Key for the ${alg} algorithm must be `, actual, ...types));
|
|
23808
23808
|
}
|
|
23809
|
-
var unusable = (
|
|
23809
|
+
var unusable = (name2, prop = "algorithm.name") => new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name2}`);
|
|
23810
23810
|
function checkUsage(key, usage) {
|
|
23811
23811
|
if (usage && !key.usages.includes(usage))
|
|
23812
23812
|
throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
@@ -24547,9 +24547,9 @@ function guardAction(binding2, tool, config2, observedAt) {
|
|
|
24547
24547
|
if (!binding2.accountId || binding2.tenantId !== config2.tenantId || binding2.deviceId !== config2.deviceId || !tool.effective) {
|
|
24548
24548
|
throw Error("GUARD_IDENTITY_REQUIRED");
|
|
24549
24549
|
}
|
|
24550
|
-
const
|
|
24551
|
-
const kind =
|
|
24552
|
-
const resourceKind = kind === "execute" ? "workspace" : kind === "localhost-access" ? "localhost-service" : kind === "git-read" ? "git-repository" :
|
|
24550
|
+
const name2 = tool.call.name;
|
|
24551
|
+
const kind = name2 === "command_run" ? "execute" : name2 === "localhost_request" ? "localhost-access" : name2 === "fs_write" || name2 === "fs_replace" ? "write" : name2.startsWith("git_") ? "git-read" : "read";
|
|
24552
|
+
const resourceKind = kind === "execute" ? "workspace" : kind === "localhost-access" ? "localhost-service" : kind === "git-read" ? "git-repository" : name2 === "fs_read" || kind === "write" ? "file" : "workspace";
|
|
24553
24553
|
return { payloadHash: guardHash(tool.effective), request: BoundActionRequestSchema.parse({
|
|
24554
24554
|
protocolVersion: 1,
|
|
24555
24555
|
requestId: binding2.executionId,
|
|
@@ -24569,13 +24569,13 @@ function guardAction(binding2, tool, config2, observedAt) {
|
|
|
24569
24569
|
adapter: guardAdapter,
|
|
24570
24570
|
actor: { kind: "human", subjectRef: binding2.accountId, authenticationClass: "delegated" },
|
|
24571
24571
|
agent: { agentRef: "grant:" + binding2.grantId },
|
|
24572
|
-
tool: { namespace: "local-commander", name },
|
|
24572
|
+
tool: { namespace: "local-commander", name: name2 },
|
|
24573
24573
|
resource: { kind: resourceKind, locatorFingerprint: guardHash({
|
|
24574
24574
|
workspace: binding2.workspaceGrantId,
|
|
24575
24575
|
resources: tool.claims,
|
|
24576
|
-
...
|
|
24576
|
+
...name2 === "localhost_request" ? { url: tool.call.args.url } : {}
|
|
24577
24577
|
}) },
|
|
24578
|
-
action: { kind, verb:
|
|
24578
|
+
action: { kind, verb: name2, effect: kind === "execute" ? "external-mutation" : kind === "write" ? "local-mutation" : kind === "localhost-access" ? "external-mutation" : "read-only" },
|
|
24579
24579
|
dataClassifications: ["unknown"],
|
|
24580
24580
|
environment: config2.environment
|
|
24581
24581
|
}
|
|
@@ -24585,7 +24585,7 @@ function guardSummary(tool) {
|
|
|
24585
24585
|
if (!tool.effective) throw Error("GUARD_ACTION_INVALID");
|
|
24586
24586
|
const call = tool.call;
|
|
24587
24587
|
const args = { ...call.args };
|
|
24588
|
-
if (args.headers) args.headers = Object.keys(args.headers).map((
|
|
24588
|
+
if (args.headers) args.headers = Object.keys(args.headers).map((name2) => name2 + " (value hidden)");
|
|
24589
24589
|
return JSON.stringify({
|
|
24590
24590
|
tool: call.name,
|
|
24591
24591
|
resources: tool.claims,
|
|
@@ -24695,7 +24695,7 @@ function canonicalTime(value) {
|
|
|
24695
24695
|
}
|
|
24696
24696
|
function validateInput(value) {
|
|
24697
24697
|
if (!value || typeof value !== "object" || Object.keys(value).some((k) => !bindingKeys.includes(k))) throw failure2("JOURNAL_INPUT_INVALID");
|
|
24698
|
-
for (const
|
|
24698
|
+
for (const name2 of bindingKeys.slice(0, 6)) if (typeof value[name2] !== "string" || !uuid11.test(value[name2])) throw failure2("JOURNAL_INPUT_INVALID");
|
|
24699
24699
|
if (value.accountId !== void 0 && !uuid11.test(value.accountId)) throw failure2("JOURNAL_INPUT_INVALID");
|
|
24700
24700
|
if (typeof value.connectionEpoch !== "string" || !/^[1-9]\d{0,18}$/.test(value.connectionEpoch) || BigInt(value.connectionEpoch) > 9223372036854775807n || typeof value.payloadHash !== "string" || !hash4.test(value.payloadHash) || !canonicalTime(value.deadlineAt)) throw failure2("JOURNAL_INPUT_INVALID");
|
|
24701
24701
|
if (value.workspaceGrantId === void 0 !== (value.policyVersion === void 0) || value.workspaceGrantId !== void 0 && (!uuid11.test(value.workspaceGrantId) || !Number.isInteger(value.policyVersion) || value.policyVersion < 1 || value.policyVersion > 2147483647)) throw failure2("JOURNAL_INPUT_INVALID");
|
|
@@ -24817,9 +24817,9 @@ var NativeJournal = class _NativeJournal {
|
|
|
24817
24817
|
async load() {
|
|
24818
24818
|
const names = await readdir(this.dir);
|
|
24819
24819
|
if (names.length > this.options.maxEntries + 256) throw failure2("JOURNAL_CAPACITY_EXCEEDED");
|
|
24820
|
-
for (const
|
|
24821
|
-
if (!
|
|
24822
|
-
const id =
|
|
24820
|
+
for (const name2 of names) {
|
|
24821
|
+
if (!name2.endsWith(".entry")) continue;
|
|
24822
|
+
const id = name2.slice(0, -6);
|
|
24823
24823
|
if (!uuid11.test(id)) throw failure2("JOURNAL_CORRUPT");
|
|
24824
24824
|
const envelope = await this.readOwned(id);
|
|
24825
24825
|
this.totalBytes += envelope.length;
|
|
@@ -25083,10 +25083,10 @@ var NativeJournal = class _NativeJournal {
|
|
|
25083
25083
|
|
|
25084
25084
|
// agent/src/private-store.ts
|
|
25085
25085
|
import { constants as constants2 } from "node:fs";
|
|
25086
|
-
import { lstat as
|
|
25087
|
-
import
|
|
25086
|
+
import { lstat as lstat3, open as open3, readFile as readFile2, readdir as readdir2, realpath as realpath3, rename as rename2, rm as rm2, writeFile } from "node:fs/promises";
|
|
25087
|
+
import path5 from "node:path";
|
|
25088
25088
|
import os from "node:os";
|
|
25089
|
-
import { createHash as
|
|
25089
|
+
import { createHash as createHash8, createPrivateKey, createPublicKey as createPublicKey3, generateKeyPairSync, randomBytes as randomBytes3, randomUUID as randomUUID3 } from "node:crypto";
|
|
25090
25090
|
import { spawn } from "node:child_process";
|
|
25091
25091
|
|
|
25092
25092
|
// src/child-environment.ts
|
|
@@ -25203,13 +25203,123 @@ function buildChildEnvironment(source, platform = process.platform) {
|
|
|
25203
25203
|
return result2;
|
|
25204
25204
|
}
|
|
25205
25205
|
|
|
25206
|
+
// agent/src/access-selection.ts
|
|
25207
|
+
import path3 from "node:path";
|
|
25208
|
+
function parseAccessSelection(value) {
|
|
25209
|
+
const fail5 = () => {
|
|
25210
|
+
throw Error("ACCESS_SELECTION_INVALID");
|
|
25211
|
+
};
|
|
25212
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail5();
|
|
25213
|
+
const input2 = value;
|
|
25214
|
+
if (!Array.isArray(input2.paths)) return fail5();
|
|
25215
|
+
if (input2.mode === "cancelled") {
|
|
25216
|
+
if (input2.paths.length !== 0) return fail5();
|
|
25217
|
+
return null;
|
|
25218
|
+
}
|
|
25219
|
+
if (input2.mode !== "folders" && input2.mode !== "all-drives") return fail5();
|
|
25220
|
+
if (input2.paths.length < 1 || input2.paths.length > (input2.mode === "folders" ? 1 : 26)) return fail5();
|
|
25221
|
+
const roots = [];
|
|
25222
|
+
for (const candidate of input2.paths) {
|
|
25223
|
+
if (typeof candidate !== "string" || candidate.length > 4096 || /[\u0000-\u001f]/.test(candidate) || !path3.win32.isAbsolute(candidate)) return fail5();
|
|
25224
|
+
if (input2.mode === "all-drives" && !/^[a-z]:\\$/i.test(candidate)) return fail5();
|
|
25225
|
+
const normalized = path3.win32.normalize(candidate).toLowerCase();
|
|
25226
|
+
if (roots.some((root) => path3.win32.normalize(root).toLowerCase() === normalized)) return fail5();
|
|
25227
|
+
roots.push(candidate);
|
|
25228
|
+
}
|
|
25229
|
+
return { mode: input2.mode, paths: roots };
|
|
25230
|
+
}
|
|
25231
|
+
|
|
25232
|
+
// agent/src/owner-permissions.ts
|
|
25233
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
25234
|
+
import { lstat as lstat2, realpath as realpath2 } from "node:fs/promises";
|
|
25235
|
+
import path4 from "node:path";
|
|
25236
|
+
var uuid12 = external_exports.string().uuid().toLowerCase();
|
|
25237
|
+
var name = external_exports.string().regex(/^[a-zA-Z0-9_.-]{1,64}$/).toLowerCase();
|
|
25238
|
+
var changeSchema = external_exports.discriminatedUnion("kind", [
|
|
25239
|
+
external_exports.object({ workspaceGrantId: uuid12, kind: external_exports.literal("write"), enabled: external_exports.boolean() }).strict(),
|
|
25240
|
+
external_exports.object({ workspaceGrantId: uuid12, kind: external_exports.literal("localhost"), enabled: external_exports.boolean(), port: external_exports.number().int().min(1).max(65535) }).strict(),
|
|
25241
|
+
external_exports.object({ workspaceGrantId: uuid12, kind: external_exports.literal("command"), enabled: external_exports.boolean(), name, executable: external_exports.string().min(1).max(4096).optional() }).strict()
|
|
25242
|
+
]);
|
|
25243
|
+
var PERMISSION_HISTORY_LIMIT = 64;
|
|
25244
|
+
var PermissionHistorySchema = external_exports.array(external_exports.object({
|
|
25245
|
+
id: uuid12,
|
|
25246
|
+
changedAt: external_exports.string().datetime(),
|
|
25247
|
+
workspaceGrantId: uuid12,
|
|
25248
|
+
kind: external_exports.enum(["write", "command", "localhost"]),
|
|
25249
|
+
enabled: external_exports.boolean(),
|
|
25250
|
+
resourceHash: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
25251
|
+
ownerSid: external_exports.string().max(184).regex(/^S-1-(?:[0-9]+-)+[0-9]+$/)
|
|
25252
|
+
}).strict()).max(PERMISSION_HISTORY_LIMIT);
|
|
25253
|
+
function permissionHash(value) {
|
|
25254
|
+
return createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
25255
|
+
}
|
|
25256
|
+
function invalid2(code = "PERMISSION_INVALID") {
|
|
25257
|
+
throw Object.assign(new Error(code), { code });
|
|
25258
|
+
}
|
|
25259
|
+
function parsePermissionChange(value) {
|
|
25260
|
+
const parsed = changeSchema.safeParse(value);
|
|
25261
|
+
if (!parsed.success) invalid2();
|
|
25262
|
+
const change = parsed.data;
|
|
25263
|
+
if (change.kind === "command" && (change.enabled ? !change.executable : change.executable !== void 0)) invalid2();
|
|
25264
|
+
return change;
|
|
25265
|
+
}
|
|
25266
|
+
function equalPath(a, b) {
|
|
25267
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
25268
|
+
}
|
|
25269
|
+
function permissionLabel(value) {
|
|
25270
|
+
return JSON.stringify(value).replace(
|
|
25271
|
+
/[\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u2069\ufeff]/gu,
|
|
25272
|
+
(ch) => "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0")
|
|
25273
|
+
);
|
|
25274
|
+
}
|
|
25275
|
+
function nonAsciiNotice(value) {
|
|
25276
|
+
if (!value || !/[^\x20-\x7e]/.test(value)) return "";
|
|
25277
|
+
return "\nWarning: this path contains non-ASCII characters: " + value.replace(/[^\x20-\x7e]/g, (ch) => "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0"));
|
|
25278
|
+
}
|
|
25279
|
+
async function planPermissionChange(current2, programs, input2) {
|
|
25280
|
+
const change = parsePermissionChange(input2);
|
|
25281
|
+
if (current2.workspaceGrantId !== change.workspaceGrantId) invalid2("WORKSPACE_UNKNOWN");
|
|
25282
|
+
const workspace = structuredClone(current2), nextPrograms = { ...programs };
|
|
25283
|
+
let resource = { rootHash: workspace.rootHash, kind: change.kind };
|
|
25284
|
+
let detail;
|
|
25285
|
+
if (change.kind === "write") {
|
|
25286
|
+
workspace.permissions.writeFiles = change.enabled;
|
|
25287
|
+
detail = "File writes: " + (change.enabled ? "ALLOW" : "DENY");
|
|
25288
|
+
} else if (change.kind === "localhost") {
|
|
25289
|
+
workspace.localhostPorts = change.enabled ? [.../* @__PURE__ */ new Set([...workspace.localhostPorts, change.port])].sort((a, b) => a - b) : workspace.localhostPorts.filter((port) => port !== change.port);
|
|
25290
|
+
if (workspace.localhostPorts.length > 32) invalid2("PERMISSION_LIMIT");
|
|
25291
|
+
workspace.permissions.localhost = workspace.localhostPorts.length > 0;
|
|
25292
|
+
resource = { rootHash: workspace.rootHash, kind: change.kind, port: change.port };
|
|
25293
|
+
detail = "Localhost port " + change.port + ": " + (change.enabled ? "ALLOW" : "DENY");
|
|
25294
|
+
} else {
|
|
25295
|
+
let executableIdentity = null;
|
|
25296
|
+
if (change.enabled) {
|
|
25297
|
+
const executable = change.executable;
|
|
25298
|
+
if (!path4.isAbsolute(executable) || path4.extname(executable).toLowerCase() !== ".exe" || /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u2069\ufeff]/u.test(executable)) invalid2();
|
|
25299
|
+
const stat = await lstat2(executable).catch(() => invalid2());
|
|
25300
|
+
const canonical2 = await realpath2(executable).catch(() => invalid2());
|
|
25301
|
+
if (!stat.isFile() || stat.isSymbolicLink() || !equalPath(path4.resolve(executable), canonical2)) invalid2();
|
|
25302
|
+
if (Object.hasOwn(nextPrograms, change.name) && !equalPath(nextPrograms[change.name], canonical2)) invalid2("COMMAND_CONFLICT");
|
|
25303
|
+
nextPrograms[change.name] = canonical2;
|
|
25304
|
+
workspace.allowedCommands = [.../* @__PURE__ */ new Set([...workspace.allowedCommands, change.name])].sort();
|
|
25305
|
+
if (workspace.allowedCommands.length > 32 || Object.keys(nextPrograms).length > 32) invalid2("PERMISSION_LIMIT");
|
|
25306
|
+
executableIdentity = { executable: canonical2, dev: stat.dev, ino: stat.ino, size: stat.size, mtimeMs: stat.mtimeMs, ctimeMs: stat.ctimeMs };
|
|
25307
|
+
} else workspace.allowedCommands = workspace.allowedCommands.filter((value) => value !== change.name);
|
|
25308
|
+
workspace.permissions.executeCommands = workspace.allowedCommands.length > 0;
|
|
25309
|
+
resource = { rootHash: workspace.rootHash, kind: change.kind, name: change.name, executable: nextPrograms[change.name] ?? null, executableIdentity };
|
|
25310
|
+
detail = "Command " + permissionLabel(change.name) + ": " + (change.enabled ? "ALLOW" : "DENY") + "\nExact executable: " + permissionLabel(nextPrograms[change.name] ?? "not registered") + nonAsciiNotice(nextPrograms[change.name]) + "\nA permitted program can access resources available to your Windows account, including outside this folder. This is not an OS sandbox.";
|
|
25311
|
+
}
|
|
25312
|
+
const summary = "Change Local Commander permissions\nWorkspace: " + permissionLabel(current2.displayName) + "\nWorkspace ID: " + current2.workspaceGrantId + "\nFolder: " + permissionLabel(current2.root) + "\n\n" + detail + "\n\nThis change persists until you revoke it. Other workspaces are not granted this permission.\nReview before approving. Closing this window, rejecting or timing out changes nothing.\nA running agent may restart after the change; running work can be interrupted. Revocation does not undo completed effects.";
|
|
25313
|
+
return { workspace, programs: nextPrograms, change, summary, resourceHash: permissionHash(resource) };
|
|
25314
|
+
}
|
|
25315
|
+
|
|
25206
25316
|
// packages/policy/src/signed-bundle.ts
|
|
25207
|
-
import { createHash as
|
|
25317
|
+
import { createHash as createHash6, KeyObject as KeyObject2 } from "node:crypto";
|
|
25208
25318
|
|
|
25209
25319
|
// packages/policy/src/evaluator.ts
|
|
25210
|
-
import { createHash as
|
|
25320
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
25211
25321
|
import { isDeepStrictEqual } from "node:util";
|
|
25212
|
-
var
|
|
25322
|
+
var uuid13 = external_exports.string().uuid().toLowerCase();
|
|
25213
25323
|
var slug2 = external_exports.string().regex(/^[a-z][a-z0-9._-]{0,63}$/);
|
|
25214
25324
|
var utc = external_exports.iso.datetime({ offset: false });
|
|
25215
25325
|
var risk = external_exports.enum(["low", "moderate", "high", "critical"]);
|
|
@@ -25234,7 +25344,7 @@ var PolicyRuleSchema = external_exports.object({
|
|
|
25234
25344
|
}
|
|
25235
25345
|
});
|
|
25236
25346
|
var PolicySnapshotSchema = external_exports.object({
|
|
25237
|
-
tenantId:
|
|
25347
|
+
tenantId: uuid13,
|
|
25238
25348
|
policyId: slug2,
|
|
25239
25349
|
policyVersion: external_exports.string().trim().min(1).max(64),
|
|
25240
25350
|
issuedAt: utc,
|
|
@@ -25264,7 +25374,7 @@ function matches(rule, request, requestRisk) {
|
|
|
25264
25374
|
return (!rule.actionKinds || rule.actionKinds.includes(action.kind)) && (!rule.toolNamespaces || rule.toolNamespaces.includes(tool.namespace)) && (!rule.environments || rule.environments.includes(environment.environment)) && (!rule.anyDataClassifications || rule.anyDataClassifications.some((value) => dataClassifications.includes(value))) && (!rule.resourceKinds || rule.resourceKinds.includes(resource.kind)) && (!rule.resourceFingerprints || rule.resourceFingerprints.includes(resource.locatorFingerprint)) && (!rule.riskClasses || rule.riskClasses.includes(requestRisk));
|
|
25265
25375
|
}
|
|
25266
25376
|
function decisionId(requestId, policyId, policyVersion, ruleId) {
|
|
25267
|
-
const bytes3 =
|
|
25377
|
+
const bytes3 = createHash5("sha256").update(JSON.stringify([requestId, policyId, policyVersion, ruleId])).digest().subarray(0, 16);
|
|
25268
25378
|
bytes3[6] = bytes3[6] & 15 | 64;
|
|
25269
25379
|
bytes3[8] = bytes3[8] & 63 | 128;
|
|
25270
25380
|
const hex3 = bytes3.toString("hex");
|
|
@@ -25333,7 +25443,7 @@ async function verifySignedPolicyBundle(candidate, authority, checkpointInput, n
|
|
|
25333
25443
|
if (payload.byteLength > 256 * 1024) throw new Error();
|
|
25334
25444
|
const bundle = BundleSchema.parse(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payload)));
|
|
25335
25445
|
const { snapshot: snapshot2 } = bundle;
|
|
25336
|
-
const payloadSha256 =
|
|
25446
|
+
const payloadSha256 = createHash6("sha256").update(payload).digest("hex");
|
|
25337
25447
|
if (snapshot2.tenantId !== expected.tenantId || snapshot2.policyId !== expected.policyId || at < Date.parse(snapshot2.issuedAt) || at >= Date.parse(snapshot2.expiresAt) || bundle.sequence < checkpoint.sequence || bundle.sequence === checkpoint.sequence && payloadSha256 !== checkpoint.payloadSha256) throw new Error();
|
|
25338
25448
|
return { snapshot: snapshot2, checkpoint: {
|
|
25339
25449
|
...expected,
|
|
@@ -25347,15 +25457,15 @@ async function verifySignedPolicyBundle(candidate, authority, checkpointInput, n
|
|
|
25347
25457
|
}
|
|
25348
25458
|
|
|
25349
25459
|
// packages/policy/src/approvals.ts
|
|
25350
|
-
import { createHash as
|
|
25351
|
-
var
|
|
25460
|
+
import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
|
|
25461
|
+
var uuid14 = TrustedActionBindingSchema.shape.tenantId;
|
|
25352
25462
|
var utc3 = TrustedActionBindingSchema.shape.expiresAt;
|
|
25353
25463
|
var digest3 = ResourceRefSchema.shape.locatorFingerprint;
|
|
25354
25464
|
var requirement = PolicyDecisionSchema.shape.approval.unwrap();
|
|
25355
25465
|
var ApprovalActionSchema = external_exports.object({ request: BoundActionRequestSchema, payloadHash: digest3 }).strict();
|
|
25356
25466
|
var ApprovalAuthoritySchema = external_exports.object({
|
|
25357
|
-
tenantId:
|
|
25358
|
-
accountId:
|
|
25467
|
+
tenantId: uuid14,
|
|
25468
|
+
accountId: uuid14,
|
|
25359
25469
|
approvalClass: requirement.shape.approvalClass,
|
|
25360
25470
|
authenticatedAt: utc3,
|
|
25361
25471
|
expiresAt: utc3
|
|
@@ -25367,11 +25477,11 @@ var ContextSchema = external_exports.object({
|
|
|
25367
25477
|
policyExpiresAt: utc3
|
|
25368
25478
|
}).strict();
|
|
25369
25479
|
var GrantSchema = external_exports.object({
|
|
25370
|
-
approvalId:
|
|
25371
|
-
requestId:
|
|
25372
|
-
decisionId:
|
|
25480
|
+
approvalId: uuid14,
|
|
25481
|
+
requestId: uuid14,
|
|
25482
|
+
decisionId: uuid14,
|
|
25373
25483
|
bindingHash: digest3,
|
|
25374
|
-
approverAccountId:
|
|
25484
|
+
approverAccountId: uuid14,
|
|
25375
25485
|
approvalClass: requirement.shape.approvalClass,
|
|
25376
25486
|
scope: requirement.shape.scope,
|
|
25377
25487
|
issuedAt: utc3,
|
|
@@ -25380,8 +25490,8 @@ var GrantSchema = external_exports.object({
|
|
|
25380
25490
|
uses: external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
|
|
25381
25491
|
}).strict().refine((value) => Date.parse(value.issuedAt) < Date.parse(value.expiresAt) && (value.scope === "once" ? value.uses === (value.state === "consumed" ? 1 : 0) : value.state !== "consumed"));
|
|
25382
25492
|
var ApprovalLedgerSchema = external_exports.object({
|
|
25383
|
-
tenantId:
|
|
25384
|
-
deviceId:
|
|
25493
|
+
tenantId: uuid14,
|
|
25494
|
+
deviceId: uuid14,
|
|
25385
25495
|
lastChangedAt: utc3,
|
|
25386
25496
|
grants: external_exports.array(GrantSchema).max(128)
|
|
25387
25497
|
}).strict().refine((value) => new Set(value.grants.map((grant) => grant.approvalId)).size === value.grants.length && new Set(value.grants.map((grant) => grant.requestId)).size === value.grants.length && value.grants.every((grant) => Date.parse(grant.issuedAt) <= Date.parse(value.lastChangedAt)));
|
|
@@ -25402,7 +25512,7 @@ function binding(ledger, input2, now) {
|
|
|
25402
25512
|
approval.expiresAt ? Date.parse(approval.expiresAt) : Infinity
|
|
25403
25513
|
);
|
|
25404
25514
|
if (at >= deadline) throw Error();
|
|
25405
|
-
const bindingHash =
|
|
25515
|
+
const bindingHash = createHash7("sha256").update(JSON.stringify([
|
|
25406
25516
|
owner.bindingSource,
|
|
25407
25517
|
owner.tenantId,
|
|
25408
25518
|
owner.accountId,
|
|
@@ -25458,7 +25568,7 @@ function issueApproval(ledgerInput, context, authorityInput, now, expiresAt) {
|
|
|
25458
25568
|
}
|
|
25459
25569
|
function consumeApproval(ledgerInput, context, approvalId, now) {
|
|
25460
25570
|
try {
|
|
25461
|
-
const ledger = current(ledgerInput, now), bound = binding(ledger, context, now), id =
|
|
25571
|
+
const ledger = current(ledgerInput, now), bound = binding(ledger, context, now), id = uuid14.parse(approvalId);
|
|
25462
25572
|
const grant = ledger.grants.find((candidate) => candidate.approvalId === id);
|
|
25463
25573
|
if (!grant || grant.state !== "active" || grant.bindingHash !== bound.bindingHash || grant.uses === Number.MAX_SAFE_INTEGER || Date.parse(now) < Date.parse(grant.issuedAt) || grant.scope === "once" && (grant.requestId !== bound.request.requestId || grant.decisionId !== bound.decision.decisionId)) throw Error();
|
|
25464
25574
|
const consumed = { ...grant, uses: grant.uses + 1, state: grant.scope === "once" ? "consumed" : "active" };
|
|
@@ -25469,7 +25579,7 @@ function consumeApproval(ledgerInput, context, approvalId, now) {
|
|
|
25469
25579
|
}
|
|
25470
25580
|
function revokeApproval(ledgerInput, approvalId, now) {
|
|
25471
25581
|
try {
|
|
25472
|
-
const ledger = current(ledgerInput, now), id =
|
|
25582
|
+
const ledger = current(ledgerInput, now), id = uuid14.parse(approvalId);
|
|
25473
25583
|
return { ...ledger, grants: ledger.grants.map((grant) => grant.approvalId === id && grant.state === "active" ? { ...grant, state: "revoked" } : grant) };
|
|
25474
25584
|
} catch {
|
|
25475
25585
|
throw Error("APPROVAL_INVALID");
|
|
@@ -25484,12 +25594,12 @@ var MAX_STATE_BYTES = 256 * 1024;
|
|
|
25484
25594
|
function fail(code) {
|
|
25485
25595
|
return Object.assign(new Error(code), { code });
|
|
25486
25596
|
}
|
|
25487
|
-
function
|
|
25488
|
-
return
|
|
25597
|
+
function equalPath2(a, b) {
|
|
25598
|
+
return path5.resolve(a).toLowerCase() === path5.resolve(b).toLowerCase();
|
|
25489
25599
|
}
|
|
25490
25600
|
function under(child, parent) {
|
|
25491
|
-
const rel =
|
|
25492
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
25601
|
+
const rel = path5.relative(parent, child);
|
|
25602
|
+
return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
25493
25603
|
}
|
|
25494
25604
|
function canonicalPublicKey(value) {
|
|
25495
25605
|
try {
|
|
@@ -25501,7 +25611,7 @@ function canonicalPublicKey(value) {
|
|
|
25501
25611
|
}
|
|
25502
25612
|
}
|
|
25503
25613
|
function fingerprint(publicKeyPem) {
|
|
25504
|
-
return
|
|
25614
|
+
return createHash8("sha256").update(canonicalPublicKey(publicKeyPem).der).digest("hex");
|
|
25505
25615
|
}
|
|
25506
25616
|
function publicFromPrivate(privateKeyPem) {
|
|
25507
25617
|
try {
|
|
@@ -25560,55 +25670,57 @@ function validateState(value) {
|
|
|
25560
25670
|
}
|
|
25561
25671
|
const approvals = value.approvals === void 0 ? void 0 : ApprovalLedgerSchema.safeParse(value.approvals);
|
|
25562
25672
|
if (approvals && (!approvals.success || approvals.data.tenantId !== enrollment?.tenantId || approvals.data.deviceId !== enrollment.deviceId)) throw fail("STATE_CORRUPT");
|
|
25673
|
+
const permissionHistory = PermissionHistorySchema.safeParse(value.permissionHistory ?? []);
|
|
25674
|
+
if (!permissionHistory.success) throw fail("STATE_CORRUPT");
|
|
25563
25675
|
const workspaces = value.workspaces ?? [];
|
|
25564
25676
|
const programs = value.programs ?? {};
|
|
25565
25677
|
const pendingWorkspaceRevokes = value.pendingWorkspaceRevokes ?? [];
|
|
25566
25678
|
if (!Array.isArray(pendingWorkspaceRevokes) || pendingWorkspaceRevokes.length > 32 || pendingWorkspaceRevokes.some((id) => typeof id !== "string" || !UUID.test(id) || id !== id.toLowerCase()) || new Set(pendingWorkspaceRevokes).size !== pendingWorkspaceRevokes.length) throw fail("STATE_CORRUPT");
|
|
25567
|
-
if (!programs || typeof programs !== "object" || Array.isArray(programs) || Object.keys(programs).length > 32 || Object.entries(programs).some(([
|
|
25568
|
-
if (!Array.isArray(workspaces) || workspaces.length > 32 || workspaces.some((w) => !w || !UUID.test(w.workspaceGrantId) || typeof w.root !== "string" || !
|
|
25679
|
+
if (!programs || typeof programs !== "object" || Array.isArray(programs) || Object.keys(programs).length > 32 || Object.entries(programs).some(([name2, executable]) => !/^[a-z0-9_.-]{1,64}$/.test(name2) || typeof executable !== "string" || !path5.isAbsolute(executable) || executable.length > 4096 || /[\u0000-\u001f]/.test(executable))) throw fail("STATE_CORRUPT");
|
|
25680
|
+
if (!Array.isArray(workspaces) || workspaces.length > 32 || workspaces.some((w) => !w || !UUID.test(w.workspaceGrantId) || typeof w.root !== "string" || !path5.isAbsolute(w.root) || w.root.length > 4096 || /[\u0000-\u001f]/.test(w.root) || !/^[a-f0-9]{64}$/.test(w.rootHash) || typeof w.displayName !== "string" || !w.displayName || w.displayName.length > 120 || w.policyVersion !== 1 || !w.permissions || w.permissions.readFiles !== true || typeof w.permissions.writeFiles !== "boolean" || typeof w.permissions.executeCommands !== "boolean" || typeof w.permissions.localhost !== "boolean" || !Array.isArray(w.allowedCommands) || w.allowedCommands.length > 32 || w.allowedCommands.some((name2) => typeof name2 !== "string" || !Object.hasOwn(programs, name2)) || new Set(w.allowedCommands).size !== w.allowedCommands.length || w.permissions.executeCommands !== w.allowedCommands.length > 0 || !Array.isArray(w.localhostPorts) || w.localhostPorts.length > 32 || w.localhostPorts.some((port) => !Number.isInteger(port) || port < 1 || port > 65535) || new Set(w.localhostPorts).size !== w.localhostPorts.length || w.permissions.localhost !== w.localhostPorts.length > 0)) throw fail("STATE_CORRUPT");
|
|
25569
25681
|
if (new Set(workspaces.map((w) => w.workspaceGrantId)).size !== workspaces.length || new Set(workspaces.map((w) => w.rootHash)).size !== workspaces.length) throw fail("STATE_CORRUPT");
|
|
25570
25682
|
if (workspaces.some((w) => pendingWorkspaceRevokes.includes(w.workspaceGrantId))) throw fail("STATE_CORRUPT");
|
|
25571
|
-
return { version: 1, publicKeyPem: value.publicKeyPem, privateKeyPem: value.privateKeyPem, fingerprint: value.fingerprint, dataKey: value.dataKey, ...enrollment ? { enrollment } : {}, workspaces, programs, pendingWorkspaceRevokes, ...policyBundle ? { policyBundle } : {}, ...approvals?.success ? { approvals: approvals.data } : {}, ...value.guardRequired ? { guardRequired: true } : {}, ...guard ? { guard } : {} };
|
|
25683
|
+
return { version: 1, publicKeyPem: value.publicKeyPem, privateKeyPem: value.privateKeyPem, fingerprint: value.fingerprint, dataKey: value.dataKey, ...enrollment ? { enrollment } : {}, workspaces, programs, pendingWorkspaceRevokes, ...value.permissionHistory ? { permissionHistory: permissionHistory.data } : {}, ...policyBundle ? { policyBundle } : {}, ...approvals?.success ? { approvals: approvals.data } : {}, ...value.guardRequired ? { guardRequired: true } : {}, ...guard ? { guard } : {} };
|
|
25572
25684
|
}
|
|
25573
25685
|
async function noSymlinkChain(target) {
|
|
25574
|
-
if (!
|
|
25575
|
-
const absolute2 =
|
|
25576
|
-
if (absolute2 ===
|
|
25686
|
+
if (!path5.isAbsolute(target)) throw fail("STORE_DIRECTORY_INVALID");
|
|
25687
|
+
const absolute2 = path5.resolve(target);
|
|
25688
|
+
if (absolute2 === path5.parse(absolute2).root) throw fail("STORE_DIRECTORY_INVALID");
|
|
25577
25689
|
const missing = [];
|
|
25578
25690
|
let cursor = absolute2;
|
|
25579
25691
|
for (; ; ) {
|
|
25580
25692
|
try {
|
|
25581
|
-
const stat = await
|
|
25693
|
+
const stat = await lstat3(cursor);
|
|
25582
25694
|
if (stat.isSymbolicLink()) throw fail("STORE_DIRECTORY_INVALID");
|
|
25583
25695
|
break;
|
|
25584
25696
|
} catch (error62) {
|
|
25585
25697
|
if (error62.code !== "ENOENT") throw error62;
|
|
25586
25698
|
missing.push(cursor);
|
|
25587
|
-
const parent =
|
|
25699
|
+
const parent = path5.dirname(cursor);
|
|
25588
25700
|
if (parent === cursor) throw fail("STORE_DIRECTORY_INVALID");
|
|
25589
25701
|
cursor = parent;
|
|
25590
25702
|
}
|
|
25591
25703
|
}
|
|
25592
|
-
const canonicalParent = await
|
|
25593
|
-
if (!
|
|
25704
|
+
const canonicalParent = await realpath3(cursor);
|
|
25705
|
+
if (!equalPath2(cursor, canonicalParent)) throw fail("STORE_DIRECTORY_INVALID");
|
|
25594
25706
|
return absolute2;
|
|
25595
25707
|
}
|
|
25596
25708
|
function allowedDirectory(absolute2) {
|
|
25597
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
25598
|
-
const appRoot =
|
|
25599
|
-
const tempRoot =
|
|
25709
|
+
const localAppData = process.env.LOCALAPPDATA || path5.join(os.homedir(), "AppData", "Local");
|
|
25710
|
+
const appRoot = path5.resolve(localAppData, "LocalCommander");
|
|
25711
|
+
const tempRoot = path5.resolve(os.tmpdir());
|
|
25600
25712
|
if (under(absolute2, appRoot)) return;
|
|
25601
|
-
if (under(absolute2, tempRoot) && /^lc-private-[A-Za-z0-9_.-]+$/i.test(
|
|
25713
|
+
if (under(absolute2, tempRoot) && /^lc-private-[A-Za-z0-9_.-]+$/i.test(path5.basename(absolute2))) return;
|
|
25602
25714
|
throw fail("STORE_DIRECTORY_POLICY");
|
|
25603
25715
|
}
|
|
25604
25716
|
async function canonicalBridge(value) {
|
|
25605
|
-
if (typeof value !== "string" || !
|
|
25606
|
-
const stat = await
|
|
25717
|
+
if (typeof value !== "string" || !path5.isAbsolute(value) || path5.extname(value).toLowerCase() !== ".exe") throw fail("BRIDGE_INVALID");
|
|
25718
|
+
const stat = await lstat3(value).catch(() => {
|
|
25607
25719
|
throw fail("BRIDGE_INVALID");
|
|
25608
25720
|
});
|
|
25609
25721
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > 4 * 1024 * 1024) throw fail("BRIDGE_INVALID");
|
|
25610
|
-
const canonical2 = await
|
|
25611
|
-
if (!
|
|
25722
|
+
const canonical2 = await realpath3(value);
|
|
25723
|
+
if (!equalPath2(value, canonical2)) throw fail("BRIDGE_INVALID");
|
|
25612
25724
|
return canonical2;
|
|
25613
25725
|
}
|
|
25614
25726
|
function parseBridgeData(value, max = MAX_STATE_BYTES * 4) {
|
|
@@ -25640,11 +25752,11 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25640
25752
|
}
|
|
25641
25753
|
const approved = /* @__PURE__ */ new Set([MARKER, "owner-state.dat", "owner-state.lock", "journal"]);
|
|
25642
25754
|
if (names.length && !names.includes(MARKER)) throw fail("STORE_DIRECTORY_UNKNOWN");
|
|
25643
|
-
if (names.some((
|
|
25755
|
+
if (names.some((name2) => !approved.has(name2))) throw fail("STORE_DIRECTORY_UNKNOWN");
|
|
25644
25756
|
await _WindowsPrivateStore.invoke(bridge, "init-directory", { path: absolute2 }, 5e3, false);
|
|
25645
|
-
const canonical2 = await
|
|
25646
|
-
if (!
|
|
25647
|
-
const markerPath =
|
|
25757
|
+
const canonical2 = await realpath3(absolute2);
|
|
25758
|
+
if (!equalPath2(absolute2, canonical2) || (await lstat3(canonical2)).isSymbolicLink()) throw fail("STORE_DIRECTORY_INVALID");
|
|
25759
|
+
const markerPath = path5.join(canonical2, MARKER);
|
|
25648
25760
|
try {
|
|
25649
25761
|
await writeFile(markerPath, "LocalCommander private state\n", { flag: "wx", mode: 384 });
|
|
25650
25762
|
} catch (error62) {
|
|
@@ -25659,10 +25771,10 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25659
25771
|
return result2;
|
|
25660
25772
|
}
|
|
25661
25773
|
statePath() {
|
|
25662
|
-
return
|
|
25774
|
+
return path5.join(this.dir, "owner-state.dat");
|
|
25663
25775
|
}
|
|
25664
25776
|
lockPath() {
|
|
25665
|
-
return
|
|
25777
|
+
return path5.join(this.dir, "owner-state.lock");
|
|
25666
25778
|
}
|
|
25667
25779
|
static invoke(bridge, arg, body, timeoutMs2, interactive, signal) {
|
|
25668
25780
|
return new Promise((resolve, reject) => {
|
|
@@ -25757,9 +25869,21 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25757
25869
|
}
|
|
25758
25870
|
}
|
|
25759
25871
|
}
|
|
25872
|
+
/** After an owner approval, a briefly busy lock (the running agent) must not discard the decision. */
|
|
25873
|
+
async withLockWait(fn, waitMs) {
|
|
25874
|
+
const deadline = Date.now() + waitMs;
|
|
25875
|
+
for (; ; ) {
|
|
25876
|
+
try {
|
|
25877
|
+
return await this.withLock(fn);
|
|
25878
|
+
} catch (error62) {
|
|
25879
|
+
if (error62.code !== "STORE_LOCKED" || Date.now() >= deadline) throw error62;
|
|
25880
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
25881
|
+
}
|
|
25882
|
+
}
|
|
25883
|
+
}
|
|
25760
25884
|
async readState() {
|
|
25761
25885
|
try {
|
|
25762
|
-
const stat = await
|
|
25886
|
+
const stat = await lstat3(this.statePath());
|
|
25763
25887
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > MAX_STATE_BYTES * 4) throw fail("STATE_CORRUPT");
|
|
25764
25888
|
const encrypted = await readFile2(this.statePath());
|
|
25765
25889
|
const plaintext = await this.unprotect(encrypted);
|
|
@@ -25773,9 +25897,9 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25773
25897
|
}
|
|
25774
25898
|
async rawHash() {
|
|
25775
25899
|
try {
|
|
25776
|
-
const stat = await
|
|
25900
|
+
const stat = await lstat3(this.statePath());
|
|
25777
25901
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_STATE_BYTES * 4) throw fail("STATE_CORRUPT");
|
|
25778
|
-
return
|
|
25902
|
+
return createHash8("sha256").update(await readFile2(this.statePath())).digest("hex");
|
|
25779
25903
|
} catch (error62) {
|
|
25780
25904
|
if (error62.code === "ENOENT") return null;
|
|
25781
25905
|
throw error62;
|
|
@@ -25786,7 +25910,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25786
25910
|
const plaintext = Buffer.from(JSON.stringify(validateState(state)), "utf8");
|
|
25787
25911
|
if (plaintext.length > MAX_STATE_BYTES) throw fail("STATE_TOO_LARGE");
|
|
25788
25912
|
const encrypted = await this.protect(plaintext);
|
|
25789
|
-
const temp =
|
|
25913
|
+
const temp = path5.join(this.dir, "owner-state." + randomBytes3(8).toString("hex") + ".tmp");
|
|
25790
25914
|
const handle = await open3(temp, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
|
|
25791
25915
|
let closed = false;
|
|
25792
25916
|
try {
|
|
@@ -25838,6 +25962,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25838
25962
|
state.workspaces = [];
|
|
25839
25963
|
state.programs = {};
|
|
25840
25964
|
state.pendingWorkspaceRevokes = [];
|
|
25965
|
+
delete state.permissionHistory;
|
|
25841
25966
|
}
|
|
25842
25967
|
state.enrollment = next;
|
|
25843
25968
|
await this.writeState(state, before);
|
|
@@ -25855,6 +25980,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25855
25980
|
state.workspaces = [];
|
|
25856
25981
|
state.programs = {};
|
|
25857
25982
|
state.pendingWorkspaceRevokes = [];
|
|
25983
|
+
delete state.permissionHistory;
|
|
25858
25984
|
await this.writeState(state, before);
|
|
25859
25985
|
}));
|
|
25860
25986
|
}
|
|
@@ -25974,7 +26100,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25974
26100
|
"guard-review",
|
|
25975
26101
|
{ data: Buffer.from(summary).toString("base64") },
|
|
25976
26102
|
timeoutMs2,
|
|
25977
|
-
|
|
26103
|
+
true,
|
|
25978
26104
|
signal
|
|
25979
26105
|
);
|
|
25980
26106
|
if (typeof answer?.approved !== "boolean" || typeof answer.ownerSid !== "string" || !/^S-1-(?:[0-9]+-)+[0-9]+$/.test(answer.ownerSid)) throw fail("GUARD_REVIEW_INVALID");
|
|
@@ -26016,7 +26142,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26016
26142
|
});
|
|
26017
26143
|
}
|
|
26018
26144
|
loadPrograms() {
|
|
26019
|
-
return this.run(async () => Object.fromEntries(Object.entries((await this.readState())?.programs ?? {}).map(([
|
|
26145
|
+
return this.run(async () => Object.fromEntries(Object.entries((await this.readState())?.programs ?? {}).map(([name2, executable]) => [name2, { executable }])));
|
|
26020
26146
|
}
|
|
26021
26147
|
loadPendingWorkspaceRevokes() {
|
|
26022
26148
|
return this.run(async () => [...(await this.readState())?.pendingWorkspaceRevokes ?? []]);
|
|
@@ -26036,7 +26162,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26036
26162
|
const before = await this.rawHash();
|
|
26037
26163
|
state.workspaces = state.workspaces.filter((w) => w.workspaceGrantId !== id);
|
|
26038
26164
|
state.pendingWorkspaceRevokes = [...state.pendingWorkspaceRevokes ?? [], id];
|
|
26039
|
-
for (const
|
|
26165
|
+
for (const name2 of Object.keys(state.programs ?? {})) if (!state.workspaces.some((w) => w.allowedCommands.includes(name2))) delete state.programs[name2];
|
|
26040
26166
|
await this.writeState(state, before);
|
|
26041
26167
|
}));
|
|
26042
26168
|
}
|
|
@@ -26049,51 +26175,112 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26049
26175
|
await this.writeState(state, before);
|
|
26050
26176
|
}));
|
|
26051
26177
|
}
|
|
26052
|
-
setWorkspacePermissions(change) {
|
|
26053
|
-
|
|
26054
|
-
|
|
26178
|
+
async setWorkspacePermissions(change) {
|
|
26179
|
+
const request = parsePermissionChange(change);
|
|
26180
|
+
const context = (state) => {
|
|
26055
26181
|
if (!state?.enrollment) throw fail("ENROLLMENT_REQUIRED");
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
|
|
26059
|
-
|
|
26060
|
-
|
|
26061
|
-
|
|
26062
|
-
|
|
26063
|
-
|
|
26064
|
-
|
|
26065
|
-
}
|
|
26066
|
-
|
|
26067
|
-
|
|
26068
|
-
|
|
26069
|
-
|
|
26070
|
-
|
|
26071
|
-
|
|
26072
|
-
|
|
26073
|
-
|
|
26074
|
-
|
|
26075
|
-
|
|
26076
|
-
|
|
26077
|
-
|
|
26078
|
-
} else workspace.allowedCommands = workspace.allowedCommands.filter((value) => value !== name);
|
|
26079
|
-
workspace.permissions.executeCommands = workspace.allowedCommands.length > 0;
|
|
26080
|
-
} else throw fail("PERMISSION_INVALID");
|
|
26081
|
-
state.workspaces = state.workspaces.map((w) => w.workspaceGrantId === workspace.workspaceGrantId ? workspace : w);
|
|
26082
|
-
for (const name of Object.keys(programs)) if (!state.workspaces.some((w) => w.allowedCommands.includes(name))) delete programs[name];
|
|
26083
|
-
state.programs = programs;
|
|
26182
|
+
const workspace = state.workspaces?.find((w) => w.workspaceGrantId === request.workspaceGrantId);
|
|
26183
|
+
if (!workspace) throw fail("WORKSPACE_UNKNOWN");
|
|
26184
|
+
const programs = state.programs ?? {};
|
|
26185
|
+
return { workspace, programs, enrollment: state.enrollment, fingerprint: permissionHash({
|
|
26186
|
+
enrollment: state.enrollment,
|
|
26187
|
+
workspace,
|
|
26188
|
+
programs: Object.entries(programs).sort(([a], [b]) => a.localeCompare(b)),
|
|
26189
|
+
guard: state.guard ?? null,
|
|
26190
|
+
guardRequired: state.guardRequired ?? false
|
|
26191
|
+
}) };
|
|
26192
|
+
};
|
|
26193
|
+
const initial = await this.run(async () => context(await this.readState()));
|
|
26194
|
+
const proposal = await planPermissionChange(initial.workspace, initial.programs, request);
|
|
26195
|
+
const review = await this.reviewGuard("Account: " + permissionLabel(initial.enrollment.accountEmail) + "\n" + proposal.summary, void 0, 6e4);
|
|
26196
|
+
if (!review.approved) throw fail("PERMISSION_REJECTED");
|
|
26197
|
+
return this.run(() => this.withLockWait(async () => {
|
|
26198
|
+
const state = await this.readState();
|
|
26199
|
+
const current2 = context(state);
|
|
26200
|
+
if (current2.fingerprint !== initial.fingerprint) throw fail("PERMISSION_STATE_CHANGED");
|
|
26201
|
+
if (state.guard && state.guard.ownerSid !== review.ownerSid) throw fail("PERMISSION_OWNER_MISMATCH");
|
|
26202
|
+
const verified = await planPermissionChange(current2.workspace, current2.programs, request);
|
|
26203
|
+
if (verified.resourceHash !== proposal.resourceHash) throw fail("PERMISSION_STATE_CHANGED");
|
|
26084
26204
|
const before = await this.rawHash();
|
|
26205
|
+
state.workspaces = state.workspaces.map((w) => w.workspaceGrantId === verified.workspace.workspaceGrantId ? verified.workspace : w);
|
|
26206
|
+
state.programs = verified.programs;
|
|
26207
|
+
for (const name2 of Object.keys(state.programs)) if (!state.workspaces.some((w) => w.allowedCommands.includes(name2))) delete state.programs[name2];
|
|
26208
|
+
const record2 = {
|
|
26209
|
+
id: randomUUID3(),
|
|
26210
|
+
changedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26211
|
+
workspaceGrantId: request.workspaceGrantId,
|
|
26212
|
+
kind: request.kind,
|
|
26213
|
+
enabled: request.enabled,
|
|
26214
|
+
resourceHash: verified.resourceHash,
|
|
26215
|
+
ownerSid: review.ownerSid
|
|
26216
|
+
};
|
|
26217
|
+
state.permissionHistory = PermissionHistorySchema.parse([...state.permissionHistory ?? [], record2].slice(-PERMISSION_HISTORY_LIMIT));
|
|
26085
26218
|
await this.writeState(state, before);
|
|
26086
|
-
return workspace;
|
|
26219
|
+
return structuredClone(verified.workspace);
|
|
26220
|
+
}, 5e3));
|
|
26221
|
+
}
|
|
26222
|
+
/** Replace scope only after an explicit local dialog; updates never call this implicitly. */
|
|
26223
|
+
async configureAccess() {
|
|
26224
|
+
const expected = await this.run(() => this.withLock(async () => {
|
|
26225
|
+
if (!(await this.readState())?.enrollment) throw fail("ENROLLMENT_REQUIRED");
|
|
26226
|
+
return this.rawHash();
|
|
26087
26227
|
}));
|
|
26228
|
+
const decision = await this.chooseAccess();
|
|
26229
|
+
if (decision === null) return null;
|
|
26230
|
+
const selected = parseAccessSelection(decision);
|
|
26231
|
+
if (!selected) return null;
|
|
26232
|
+
const roots = [];
|
|
26233
|
+
for (const chosen of selected.paths) {
|
|
26234
|
+
const stat = await lstat3(chosen);
|
|
26235
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw fail("WORKSPACE_INVALID");
|
|
26236
|
+
const root = await realpath3(chosen);
|
|
26237
|
+
const isDrive = equalPath2(root, path5.parse(root).root);
|
|
26238
|
+
if (!equalPath2(root, chosen) || (selected.mode === "all-drives" ? !isDrive : isDrive) || equalPath2(root, this.dir) || under(root, this.dir) || selected.mode === "folders" && under(this.dir, root)) throw fail("WORKSPACE_INVALID");
|
|
26239
|
+
roots.push(root);
|
|
26240
|
+
}
|
|
26241
|
+
return this.run(() => this.withLock(async () => {
|
|
26242
|
+
if (await this.rawHash() !== expected) throw fail("STATE_CHANGED");
|
|
26243
|
+
const state = await this.readState();
|
|
26244
|
+
if (!state?.enrollment) throw fail("ENROLLMENT_REQUIRED");
|
|
26245
|
+
const workspaces = roots.map((root) => {
|
|
26246
|
+
const rootHash = createHash8("sha256").update(process.platform === "win32" ? root.toLowerCase() : root).digest("hex");
|
|
26247
|
+
const existing = state.workspaces?.find((w) => w.rootHash === rootHash);
|
|
26248
|
+
return {
|
|
26249
|
+
workspaceGrantId: existing?.workspaceGrantId ?? randomUUID3(),
|
|
26250
|
+
root,
|
|
26251
|
+
rootHash,
|
|
26252
|
+
displayName: (path5.basename(root) || root).slice(0, 120),
|
|
26253
|
+
policyVersion: 1,
|
|
26254
|
+
permissions: { readFiles: true, writeFiles: true, executeCommands: false, localhost: false },
|
|
26255
|
+
allowedCommands: [],
|
|
26256
|
+
localhostPorts: []
|
|
26257
|
+
};
|
|
26258
|
+
});
|
|
26259
|
+
const retained = new Set(workspaces.map((w) => w.workspaceGrantId));
|
|
26260
|
+
const removed = (state.workspaces ?? []).filter((w) => !retained.has(w.workspaceGrantId)).map((w) => w.workspaceGrantId);
|
|
26261
|
+
const pending = [.../* @__PURE__ */ new Set([...state.pendingWorkspaceRevokes ?? [], ...removed])];
|
|
26262
|
+
if (pending.length > 32) throw fail("WORKSPACE_REVOKE_LIMIT");
|
|
26263
|
+
state.workspaces = workspaces;
|
|
26264
|
+
state.programs = {};
|
|
26265
|
+
state.pendingWorkspaceRevokes = pending;
|
|
26266
|
+
await this.writeState(state, expected);
|
|
26267
|
+
return structuredClone(workspaces);
|
|
26268
|
+
}));
|
|
26269
|
+
}
|
|
26270
|
+
chooseAccess() {
|
|
26271
|
+
return this.run(async () => parseAccessSelection(await _WindowsPrivateStore.invoke(this.bridge, "choose-access", {}, 24e4, true)));
|
|
26272
|
+
}
|
|
26273
|
+
loadPermissionHistory() {
|
|
26274
|
+
return this.run(async () => structuredClone((await this.readState())?.permissionHistory ?? []));
|
|
26088
26275
|
}
|
|
26089
26276
|
async approveWorkspace() {
|
|
26090
26277
|
const chosen = await this.chooseFolder();
|
|
26091
26278
|
if (chosen === null) return null;
|
|
26092
|
-
const stat = await
|
|
26279
|
+
const stat = await lstat3(chosen);
|
|
26093
26280
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw fail("WORKSPACE_INVALID");
|
|
26094
|
-
const root = await
|
|
26095
|
-
if (!
|
|
26096
|
-
const rootHash =
|
|
26281
|
+
const root = await realpath3(chosen);
|
|
26282
|
+
if (!equalPath2(root, chosen) || equalPath2(root, path5.parse(root).root) || equalPath2(root, this.dir) || under(this.dir, root)) throw fail("WORKSPACE_INVALID");
|
|
26283
|
+
const rootHash = createHash8("sha256").update(process.platform === "win32" ? root.toLowerCase() : root).digest("hex");
|
|
26097
26284
|
return this.run(() => this.withLock(async () => {
|
|
26098
26285
|
const state = await this.readState();
|
|
26099
26286
|
if (!state?.enrollment) throw fail("ENROLLMENT_REQUIRED");
|
|
@@ -26101,7 +26288,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26101
26288
|
if (existing) return existing;
|
|
26102
26289
|
if ((state.workspaces?.length ?? 0) >= 32) throw fail("WORKSPACE_LIMIT");
|
|
26103
26290
|
const before = await this.rawHash();
|
|
26104
|
-
const workspace = { workspaceGrantId: randomUUID3(), root, rootHash, displayName:
|
|
26291
|
+
const workspace = { workspaceGrantId: randomUUID3(), root, rootHash, displayName: path5.basename(root).slice(0, 120), policyVersion: 1, permissions: { readFiles: true, writeFiles: true, executeCommands: false, localhost: false }, allowedCommands: [], localhostPorts: [] };
|
|
26105
26292
|
state.workspaces = [...state.workspaces ?? [], workspace];
|
|
26106
26293
|
await this.writeState(state, before);
|
|
26107
26294
|
return workspace;
|
|
@@ -26111,7 +26298,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26111
26298
|
return this.run(async () => {
|
|
26112
26299
|
const result2 = await _WindowsPrivateStore.invoke(this.bridge, "choose-folder", {}, 12e4, true);
|
|
26113
26300
|
if (result2.path === null) return null;
|
|
26114
|
-
if (typeof result2.path !== "string" || !
|
|
26301
|
+
if (typeof result2.path !== "string" || !path5.isAbsolute(result2.path) || result2.path.length > 4096 || /[\u0000-\u001f]/.test(result2.path)) throw fail("BRIDGE_OUTPUT_INVALID");
|
|
26115
26302
|
return result2.path;
|
|
26116
26303
|
});
|
|
26117
26304
|
}
|
|
@@ -26119,22 +26306,40 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26119
26306
|
|
|
26120
26307
|
// agent/src/tools.ts
|
|
26121
26308
|
import { constants as constants3 } from "node:fs";
|
|
26122
|
-
import { lstat as
|
|
26123
|
-
import
|
|
26124
|
-
import { createHash as
|
|
26309
|
+
import { lstat as lstat5, open as open4, readdir as readdir3, realpath as realpath5, rename as rename3, rm as rm3 } from "node:fs/promises";
|
|
26310
|
+
import path9 from "node:path";
|
|
26311
|
+
import { createHash as createHash9, randomUUID as randomUUID4 } from "node:crypto";
|
|
26125
26312
|
|
|
26126
26313
|
// src/guard.ts
|
|
26127
|
-
import { lstat as
|
|
26128
|
-
import
|
|
26314
|
+
import { lstat as lstat4, realpath as realpath4 } from "node:fs/promises";
|
|
26315
|
+
import path6 from "node:path";
|
|
26129
26316
|
var SENSITIVE_NAMES = /* @__PURE__ */ new Set([
|
|
26130
26317
|
".npmrc",
|
|
26131
26318
|
".pypirc",
|
|
26132
26319
|
".git-credentials",
|
|
26133
26320
|
"id_rsa",
|
|
26134
26321
|
"id_ed25519",
|
|
26135
|
-
"
|
|
26322
|
+
"id_ecdsa",
|
|
26323
|
+
"id_dsa",
|
|
26324
|
+
".netrc",
|
|
26325
|
+
"_netrc",
|
|
26326
|
+
".pgpass",
|
|
26327
|
+
"credentials.json",
|
|
26328
|
+
"application_default_credentials.json"
|
|
26136
26329
|
]);
|
|
26137
|
-
var SENSITIVE_EXTENSIONS = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx"]);
|
|
26330
|
+
var SENSITIVE_EXTENSIONS = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx", ".ppk", ".kdbx"]);
|
|
26331
|
+
var SENSITIVE_IN_DIRECTORY = [
|
|
26332
|
+
[".aws", "credentials"],
|
|
26333
|
+
[".kube", "config"],
|
|
26334
|
+
[".docker", "config.json"],
|
|
26335
|
+
[".claude", ".credentials.json"],
|
|
26336
|
+
[".codex", "auth.json"],
|
|
26337
|
+
[".azure", "msal_token_cache.json"],
|
|
26338
|
+
[".azure", "accesstokens.json"],
|
|
26339
|
+
["gh", "hosts.yml"],
|
|
26340
|
+
["github cli", "hosts.yml"]
|
|
26341
|
+
];
|
|
26342
|
+
var SENSITIVE_DIRECTORIES = /* @__PURE__ */ new Set([".ssh", ".gnupg"]);
|
|
26138
26343
|
var PROTECTED_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
26139
26344
|
".local-commander-oauth.json",
|
|
26140
26345
|
"local-commander.config.json"
|
|
@@ -26144,12 +26349,12 @@ function comparePath(value) {
|
|
|
26144
26349
|
return process.platform === "win32" ? value.toLowerCase() : value;
|
|
26145
26350
|
}
|
|
26146
26351
|
function isInside(root, candidate) {
|
|
26147
|
-
const relative =
|
|
26148
|
-
return relative === "" || !
|
|
26352
|
+
const relative = path6.relative(root, candidate);
|
|
26353
|
+
return relative === "" || !path6.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path6.sep}`);
|
|
26149
26354
|
}
|
|
26150
26355
|
function splitPathSegments(filePath) {
|
|
26151
|
-
const parsed =
|
|
26152
|
-
return
|
|
26356
|
+
const parsed = path6.parse(path6.resolve(filePath));
|
|
26357
|
+
return path6.relative(parsed.root, path6.resolve(filePath)).split(path6.sep).filter((segment) => segment.length > 0);
|
|
26153
26358
|
}
|
|
26154
26359
|
function isProtectedAuditFile(base) {
|
|
26155
26360
|
return base === ".local-commander-audit.jsonl" || base.startsWith(".local-commander-audit.jsonl.");
|
|
@@ -26164,37 +26369,40 @@ function looksProtected(filePath) {
|
|
|
26164
26369
|
return false;
|
|
26165
26370
|
}
|
|
26166
26371
|
function looksSensitive(filePath) {
|
|
26167
|
-
const base =
|
|
26372
|
+
const base = path6.basename(filePath).toLowerCase();
|
|
26168
26373
|
if (base === ".env" || base.startsWith(".env.") && base !== ".env.example") return true;
|
|
26169
26374
|
if (SENSITIVE_NAMES.has(base)) return true;
|
|
26170
|
-
if (SENSITIVE_EXTENSIONS.has(
|
|
26171
|
-
|
|
26375
|
+
if (SENSITIVE_EXTENSIONS.has(path6.extname(base))) return true;
|
|
26376
|
+
const segments = splitPathSegments(filePath).map((segment) => segment.toLowerCase());
|
|
26377
|
+
if (segments.slice(0, -1).some((segment) => SENSITIVE_DIRECTORIES.has(segment))) return true;
|
|
26378
|
+
const parent = segments.at(-2);
|
|
26379
|
+
return SENSITIVE_IN_DIRECTORY.some(([directory, name2]) => directory === parent && name2 === base);
|
|
26172
26380
|
}
|
|
26173
26381
|
async function canonicalizePossiblyMissing(inputPath, base) {
|
|
26174
|
-
const absolute2 =
|
|
26175
|
-
const parsed =
|
|
26382
|
+
const absolute2 = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(base, inputPath);
|
|
26383
|
+
const parsed = path6.parse(absolute2);
|
|
26176
26384
|
let current2 = absolute2;
|
|
26177
26385
|
const missing = [];
|
|
26178
26386
|
while (current2 !== parsed.root) {
|
|
26179
26387
|
try {
|
|
26180
|
-
const resolved = await
|
|
26181
|
-
return
|
|
26388
|
+
const resolved = await realpath4(current2);
|
|
26389
|
+
return path6.join(resolved, ...missing.reverse());
|
|
26182
26390
|
} catch (error62) {
|
|
26183
26391
|
if (error62.code !== "ENOENT") throw error62;
|
|
26184
26392
|
let exists = false;
|
|
26185
26393
|
try {
|
|
26186
|
-
await
|
|
26394
|
+
await lstat4(current2);
|
|
26187
26395
|
exists = true;
|
|
26188
26396
|
} catch (statError) {
|
|
26189
26397
|
if (statError.code !== "ENOENT") throw statError;
|
|
26190
26398
|
}
|
|
26191
26399
|
if (exists) throw error62;
|
|
26192
|
-
missing.push(
|
|
26193
|
-
current2 =
|
|
26400
|
+
missing.push(path6.basename(current2));
|
|
26401
|
+
current2 = path6.dirname(current2);
|
|
26194
26402
|
}
|
|
26195
26403
|
}
|
|
26196
|
-
const root = await
|
|
26197
|
-
return
|
|
26404
|
+
const root = await realpath4(parsed.root);
|
|
26405
|
+
return path6.join(root, ...missing.reverse());
|
|
26198
26406
|
}
|
|
26199
26407
|
var PathGuard = class _PathGuard {
|
|
26200
26408
|
constructor(roots, allowSensitiveFiles, protectedPaths) {
|
|
@@ -26210,11 +26418,11 @@ var PathGuard = class _PathGuard {
|
|
|
26210
26418
|
for (const root of roots) {
|
|
26211
26419
|
let resolved;
|
|
26212
26420
|
try {
|
|
26213
|
-
resolved = await
|
|
26421
|
+
resolved = await realpath4(path6.resolve(root));
|
|
26214
26422
|
} catch {
|
|
26215
26423
|
throw new Error(`Configured root does not exist: ${root}`);
|
|
26216
26424
|
}
|
|
26217
|
-
const stat = await
|
|
26425
|
+
const stat = await lstat4(resolved);
|
|
26218
26426
|
if (!stat.isDirectory()) throw new Error(`Configured root is not a directory: ${root}`);
|
|
26219
26427
|
if (!canonical2.some((existing) => comparePath(existing) === comparePath(resolved))) {
|
|
26220
26428
|
canonical2.push(resolved);
|
|
@@ -26223,8 +26431,8 @@ var PathGuard = class _PathGuard {
|
|
|
26223
26431
|
if (canonical2.length === 0) throw new Error("At least one allowed root is required");
|
|
26224
26432
|
const protectedPaths = [];
|
|
26225
26433
|
for (const protectedPath of options.protectedPaths ?? []) {
|
|
26226
|
-
const base =
|
|
26227
|
-
const absolute2 =
|
|
26434
|
+
const base = path6.isAbsolute(protectedPath) ? path6.parse(path6.resolve(protectedPath)).root : canonical2[0];
|
|
26435
|
+
const absolute2 = path6.resolve(base, protectedPath);
|
|
26228
26436
|
if (!protectedPaths.some((existing) => comparePath(existing) === comparePath(absolute2))) protectedPaths.push(absolute2);
|
|
26229
26437
|
const resolved = await canonicalizePossiblyMissing(protectedPath, base);
|
|
26230
26438
|
if (!protectedPaths.some((existing) => comparePath(existing) === comparePath(resolved))) {
|
|
@@ -26240,7 +26448,7 @@ var PathGuard = class _PathGuard {
|
|
|
26240
26448
|
}
|
|
26241
26449
|
assertNotProtected(candidate) {
|
|
26242
26450
|
if (looksProtected(candidate)) {
|
|
26243
|
-
throw new Error(`Local Commander protected state is blocked: ${
|
|
26451
|
+
throw new Error(`Local Commander protected state is blocked: ${path6.basename(candidate)}`);
|
|
26244
26452
|
}
|
|
26245
26453
|
const normalizedCandidate = comparePath(candidate);
|
|
26246
26454
|
for (const protectedPath of this.protectedPaths) {
|
|
@@ -26253,37 +26461,37 @@ var PathGuard = class _PathGuard {
|
|
|
26253
26461
|
this.assertNotProtected(filePath);
|
|
26254
26462
|
if (!this.allowSensitiveFiles && looksSensitive(filePath)) {
|
|
26255
26463
|
throw new Error(
|
|
26256
|
-
`Sensitive file access is blocked by default: ${
|
|
26464
|
+
`Sensitive file access is blocked by default: ${path6.basename(filePath)}. Enable files.allowSensitiveFiles only if you really need it.`
|
|
26257
26465
|
);
|
|
26258
26466
|
}
|
|
26259
26467
|
}
|
|
26260
26468
|
async resolveExisting(inputPath, cwd) {
|
|
26261
26469
|
const base = cwd ? await this.resolveExisting(cwd) : this.roots[0];
|
|
26262
|
-
const candidate =
|
|
26470
|
+
const candidate = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(base, inputPath);
|
|
26263
26471
|
this.assertNotProtected(candidate);
|
|
26264
|
-
const resolved = await
|
|
26472
|
+
const resolved = await realpath4(candidate);
|
|
26265
26473
|
this.assertWithinRoots(resolved);
|
|
26266
26474
|
this.assertNotProtected(resolved);
|
|
26267
26475
|
return resolved;
|
|
26268
26476
|
}
|
|
26269
26477
|
async resolveDirectory(inputPath, cwd) {
|
|
26270
26478
|
const resolved = await this.resolveExisting(inputPath, cwd);
|
|
26271
|
-
const stat = await
|
|
26479
|
+
const stat = await lstat4(resolved);
|
|
26272
26480
|
if (!stat.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
26273
26481
|
return resolved;
|
|
26274
26482
|
}
|
|
26275
26483
|
async resolveForWrite(inputPath, cwd) {
|
|
26276
26484
|
const base = cwd ? await this.resolveDirectory(cwd) : this.roots[0];
|
|
26277
|
-
const candidate =
|
|
26485
|
+
const candidate = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(base, inputPath);
|
|
26278
26486
|
this.assertNotProtected(candidate);
|
|
26279
26487
|
let existing;
|
|
26280
26488
|
try {
|
|
26281
|
-
existing = await
|
|
26489
|
+
existing = await realpath4(candidate);
|
|
26282
26490
|
} catch (error62) {
|
|
26283
26491
|
if (error62.code !== "ENOENT") throw error62;
|
|
26284
26492
|
let exists = false;
|
|
26285
26493
|
try {
|
|
26286
|
-
await
|
|
26494
|
+
await lstat4(candidate);
|
|
26287
26495
|
exists = true;
|
|
26288
26496
|
} catch (statError) {
|
|
26289
26497
|
if (statError.code !== "ENOENT") throw statError;
|
|
@@ -26296,11 +26504,11 @@ var PathGuard = class _PathGuard {
|
|
|
26296
26504
|
this.assertSafeFile(existing);
|
|
26297
26505
|
return existing;
|
|
26298
26506
|
}
|
|
26299
|
-
const parent = await
|
|
26507
|
+
const parent = await realpath4(path6.dirname(candidate));
|
|
26300
26508
|
this.assertWithinRoots(parent);
|
|
26301
26509
|
this.assertNotProtected(parent);
|
|
26302
26510
|
this.assertSafeFile(candidate);
|
|
26303
|
-
const target =
|
|
26511
|
+
const target = path6.join(parent, path6.basename(candidate));
|
|
26304
26512
|
this.assertSafeFile(target);
|
|
26305
26513
|
return target;
|
|
26306
26514
|
}
|
|
@@ -26311,12 +26519,12 @@ var PathGuard = class _PathGuard {
|
|
|
26311
26519
|
|
|
26312
26520
|
// src/windows-file-read.ts
|
|
26313
26521
|
import { execFile } from "node:child_process";
|
|
26314
|
-
import
|
|
26522
|
+
import path7 from "node:path";
|
|
26315
26523
|
function readWindowsFile(bridge, file2, maxBytes, signal) {
|
|
26316
26524
|
return invokeRead(bridge, file2, maxBytes, false, signal);
|
|
26317
26525
|
}
|
|
26318
26526
|
function invokeRead(bridge, file2, maxBytes, prefix, signal) {
|
|
26319
|
-
if (process.platform !== "win32" || !
|
|
26527
|
+
if (process.platform !== "win32" || !path7.isAbsolute(bridge) || !path7.isAbsolute(file2) || !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > (prefix ? 16777216 : 1048576)) {
|
|
26320
26528
|
return Promise.reject(new Error("FILE_READ_ARGUMENT_INVALID"));
|
|
26321
26529
|
}
|
|
26322
26530
|
if (signal?.aborted) return Promise.reject(new Error("CANCELLED"));
|
|
@@ -26345,9 +26553,9 @@ function invokeRead(bridge, file2, maxBytes, prefix, signal) {
|
|
|
26345
26553
|
|
|
26346
26554
|
// agent/src/job-supervisor.ts
|
|
26347
26555
|
import { spawn as spawn2 } from "node:child_process";
|
|
26348
|
-
import
|
|
26556
|
+
import path8 from "node:path";
|
|
26349
26557
|
function absolute(value) {
|
|
26350
|
-
return typeof value === "string" && value.length > 0 && value.length < 32767 && !value.includes("\0") &&
|
|
26558
|
+
return typeof value === "string" && value.length > 0 && value.length < 32767 && !value.includes("\0") && path8.isAbsolute(value) && path8.resolve(value) === value;
|
|
26351
26559
|
}
|
|
26352
26560
|
async function runSupervised(helper, request, options = {}) {
|
|
26353
26561
|
if (process.platform !== "win32") throw Error("WINDOWS_SUPERVISOR_REQUIRED");
|
|
@@ -26363,7 +26571,7 @@ async function runSupervised(helper, request, options = {}) {
|
|
|
26363
26571
|
let pid = null, outcome = null;
|
|
26364
26572
|
let buffer = "", failure5 = null, closed = false, cancelSent = false;
|
|
26365
26573
|
let killTimer, hardTimer;
|
|
26366
|
-
const child = spawn2(helper, [], { cwd:
|
|
26574
|
+
const child = spawn2(helper, [], { cwd: path8.dirname(helper), stdio: ["pipe", "pipe", "pipe"], windowsHide: true, shell: false, env: buildChildEnvironment(process.env) });
|
|
26367
26575
|
const cancel = () => {
|
|
26368
26576
|
if (closed || cancelSent) return;
|
|
26369
26577
|
cancelSent = true;
|
|
@@ -26465,9 +26673,9 @@ function fail2(code) {
|
|
|
26465
26673
|
throw new NativeToolError(code);
|
|
26466
26674
|
}
|
|
26467
26675
|
function hash5(value) {
|
|
26468
|
-
return
|
|
26676
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
26469
26677
|
}
|
|
26470
|
-
function
|
|
26678
|
+
function equalPath3(a, b) {
|
|
26471
26679
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
26472
26680
|
}
|
|
26473
26681
|
function result(body) {
|
|
@@ -26479,7 +26687,7 @@ function stopped(signal) {
|
|
|
26479
26687
|
if (signal.aborted) fail2("CANCELLED");
|
|
26480
26688
|
}
|
|
26481
26689
|
async function readBoundedFile(file2, maxBytes, bridge, signal) {
|
|
26482
|
-
const stat = await
|
|
26690
|
+
const stat = await lstat5(file2);
|
|
26483
26691
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > maxBytes) fail2("FILE_UNSUPPORTED_OR_TOO_LARGE");
|
|
26484
26692
|
if (process.platform === "win32") return readWindowsFile(bridge, file2, maxBytes, signal);
|
|
26485
26693
|
const handle = await open4(file2, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
@@ -26517,7 +26725,7 @@ var NativeTools = class {
|
|
|
26517
26725
|
options;
|
|
26518
26726
|
async readFile(file2, maxBytes, signal) {
|
|
26519
26727
|
try {
|
|
26520
|
-
return await readBoundedFile(file2, maxBytes, this.options.ownerBridgePath ??
|
|
26728
|
+
return await readBoundedFile(file2, maxBytes, this.options.ownerBridgePath ?? path9.join(path9.dirname(this.options.helperPath), "OwnerBridge.exe"), signal);
|
|
26521
26729
|
} catch (error62) {
|
|
26522
26730
|
if (error62.code === "ENOENT") throw error62;
|
|
26523
26731
|
if (error62 instanceof Error && /^[A-Z_]{1,40}$/.test(error62.message)) fail2(error62.message);
|
|
@@ -26540,9 +26748,19 @@ var NativeTools = class {
|
|
|
26540
26748
|
if (scope2 === "files:read" && !permission.readFiles || scope2 === "files:write" && !permission.writeFiles || scope2 === "commands:execute" && !permission.executeCommands || scope2 === "localhost:access" && !permission.localhost) fail2("LOCAL_PERMISSION_REQUIRED");
|
|
26541
26749
|
let guard;
|
|
26542
26750
|
try {
|
|
26543
|
-
const canonical2 = await
|
|
26544
|
-
if (!
|
|
26545
|
-
|
|
26751
|
+
const canonical2 = await realpath5(workspace.root);
|
|
26752
|
+
if (!equalPath3(canonical2, workspace.root) || hash5(process.platform === "win32" ? canonical2.toLowerCase() : canonical2) !== workspace.rootHash) fail2("WORKSPACE_CHANGED");
|
|
26753
|
+
const runtimeDirectory = path9.dirname(this.options.helperPath);
|
|
26754
|
+
const protectedPaths = [
|
|
26755
|
+
this.options.stateDirectory,
|
|
26756
|
+
this.options.helperPath,
|
|
26757
|
+
...["agent.mjs", "node.exe", "OwnerBridge.exe", "LocalCommanderAgent.exe"].map((name2) => path9.join(runtimeDirectory, name2))
|
|
26758
|
+
];
|
|
26759
|
+
if (this.options.ownerBridgePath) protectedPaths.push(this.options.ownerBridgePath);
|
|
26760
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA && path9.isAbsolute(process.env.LOCALAPPDATA)) {
|
|
26761
|
+
protectedPaths.push(path9.join(process.env.LOCALAPPDATA, "LocalCommander"));
|
|
26762
|
+
}
|
|
26763
|
+
guard = await PathGuard.create([canonical2], false, { protectedPaths });
|
|
26546
26764
|
} catch (e) {
|
|
26547
26765
|
if (e instanceof NativeToolError) throw e;
|
|
26548
26766
|
fail2("WORKSPACE_UNAVAILABLE");
|
|
@@ -26568,11 +26786,11 @@ var NativeTools = class {
|
|
|
26568
26786
|
return { call, claims: readClaim(filename), run: async (signal) => {
|
|
26569
26787
|
stopped(signal);
|
|
26570
26788
|
const verified = await resolve(call.args.path);
|
|
26571
|
-
if (!
|
|
26789
|
+
if (!equalPath3(verified, filename)) fail2("FILE_CHANGED");
|
|
26572
26790
|
const bytes3 = await this.readFile(filename, 1048576, signal), contents = utf8(bytes3), lines = contents.split(/\r?\n/);
|
|
26573
26791
|
const selected = lines.slice(first - 1, Math.min(last, lines.length)).join("\n");
|
|
26574
26792
|
if (Buffer.byteLength(selected) > 12e4) fail2("OUTPUT_LIMIT_EXCEEDED");
|
|
26575
|
-
return result({ path:
|
|
26793
|
+
return result({ path: path9.relative(workspace.root, filename), sha256: hash5(bytes3), startLine: first, endLine: Math.min(last, lines.length), totalLines: lines.length, content: selected, truncated: last < lines.length });
|
|
26576
26794
|
} };
|
|
26577
26795
|
}
|
|
26578
26796
|
if (call.name === "fs_write" || call.name === "fs_replace") {
|
|
@@ -26587,7 +26805,7 @@ var NativeTools = class {
|
|
|
26587
26805
|
return { call, claims: writeClaim(filename), run: async (signal) => {
|
|
26588
26806
|
stopped(signal);
|
|
26589
26807
|
const target = await guard.resolveForWrite(args.path);
|
|
26590
|
-
if (!
|
|
26808
|
+
if (!equalPath3(filename, target)) fail2("FILE_CHANGED");
|
|
26591
26809
|
let old = null;
|
|
26592
26810
|
try {
|
|
26593
26811
|
old = await this.readFile(target, 1048576, signal);
|
|
@@ -26608,7 +26826,7 @@ var NativeTools = class {
|
|
|
26608
26826
|
}
|
|
26609
26827
|
const bytes3 = Buffer.from(contents, "utf8");
|
|
26610
26828
|
if (bytes3.length > MAX_FILE_WRITE_BYTES) fail2("FILE_WRITE_LIMIT");
|
|
26611
|
-
const temp =
|
|
26829
|
+
const temp = path9.join(path9.dirname(target), ".lc-write-" + randomUUID4() + ".tmp");
|
|
26612
26830
|
const handle = await open4(temp, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
26613
26831
|
try {
|
|
26614
26832
|
await handle.writeFile(bytes3);
|
|
@@ -26616,7 +26834,7 @@ var NativeTools = class {
|
|
|
26616
26834
|
await handle.close();
|
|
26617
26835
|
stopped(signal);
|
|
26618
26836
|
const again = await guard.resolveForWrite(args.path);
|
|
26619
|
-
if (!
|
|
26837
|
+
if (!equalPath3(target, again)) fail2("FILE_CHANGED");
|
|
26620
26838
|
if (old) {
|
|
26621
26839
|
if (hash5(await this.readFile(target, 1048576, signal)) !== hash5(old)) fail2("FILE_CHANGED");
|
|
26622
26840
|
await rename3(temp, target);
|
|
@@ -26625,7 +26843,7 @@ var NativeTools = class {
|
|
|
26625
26843
|
await link(temp, target);
|
|
26626
26844
|
await rm3(temp);
|
|
26627
26845
|
}
|
|
26628
|
-
return result({ path:
|
|
26846
|
+
return result({ path: path9.relative(workspace.root, target), bytes: bytes3.length, sha256: hash5(bytes3), ...call.name === "fs_replace" ? { replacements } : {} });
|
|
26629
26847
|
} catch (e) {
|
|
26630
26848
|
await handle.close().catch(() => {
|
|
26631
26849
|
});
|
|
@@ -26646,12 +26864,12 @@ var NativeTools = class {
|
|
|
26646
26864
|
stopped(signal);
|
|
26647
26865
|
if (matches2.length >= call.args.maxResults || scanned >= 300) break;
|
|
26648
26866
|
try {
|
|
26649
|
-
const filename = await guard.resolveExisting(
|
|
26867
|
+
const filename = await guard.resolveExisting(path9.join(base, entry.path));
|
|
26650
26868
|
guard.assertSafeFile(filename);
|
|
26651
26869
|
const data = utf8(await this.readFile(filename, 256e3, signal));
|
|
26652
26870
|
scanned++;
|
|
26653
26871
|
const lines = data.split(/\r?\n/);
|
|
26654
|
-
for (let i = 0; i < lines.length && matches2.length < call.args.maxResults; i++) if ((call.args.caseSensitive ? lines[i] : lines[i].toLowerCase()).includes(query)) matches2.push({ path:
|
|
26872
|
+
for (let i = 0; i < lines.length && matches2.length < call.args.maxResults; i++) if ((call.args.caseSensitive ? lines[i] : lines[i].toLowerCase()).includes(query)) matches2.push({ path: path9.relative(workspace.root, filename), line: i + 1, text: lines[i].slice(0, 500) });
|
|
26655
26873
|
} catch (error62) {
|
|
26656
26874
|
if (error62 instanceof NativeToolError && ["FILE_READ_FAILED", "CANCELLED"].includes(error62.code)) throw error62;
|
|
26657
26875
|
}
|
|
@@ -26663,9 +26881,9 @@ var NativeTools = class {
|
|
|
26663
26881
|
const url2 = new URL(call.args.url), port = Number(url2.port || (url2.protocol === "https:" ? 443 : 80));
|
|
26664
26882
|
if (!["http:", "https:"].includes(url2.protocol) || !["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname) || url2.username || url2.password || url2.hash || !workspace.localhostPorts.includes(port) || this.options.protectedPorts?.includes(port)) fail2("LOCALHOST_NOT_ALLOWED");
|
|
26665
26883
|
const headers = {};
|
|
26666
|
-
for (const [
|
|
26667
|
-
if (!/^[A-Za-z0-9-]+$/.test(
|
|
26668
|
-
headers[
|
|
26884
|
+
for (const [name2, value] of Object.entries(call.args.headers ?? {})) {
|
|
26885
|
+
if (!/^[A-Za-z0-9-]+$/.test(name2) || /[\r\n]/.test(value) || ["host", "connection", "content-length", "transfer-encoding", "upgrade", "proxy-authorization", "proxy-connection"].includes(name2.toLowerCase()) || Object.keys(headers).some((k) => k.toLowerCase() === name2.toLowerCase())) fail2("INVALID_HTTP_HEADER");
|
|
26886
|
+
headers[name2] = value;
|
|
26669
26887
|
}
|
|
26670
26888
|
if (Object.keys(headers).length > 32 || ["GET", "HEAD"].includes(call.args.method) && call.args.body !== void 0) fail2("INVALID_HTTP_ARGUMENTS");
|
|
26671
26889
|
if (url2.hostname === "localhost") url2.hostname = "127.0.0.1";
|
|
@@ -26673,9 +26891,9 @@ var NativeTools = class {
|
|
|
26673
26891
|
const combined = AbortSignal.any([signal, AbortSignal.timeout(1e4)]);
|
|
26674
26892
|
const response = await fetch(url2, { method: call.args.method, headers, body: call.args.body, redirect: "manual", signal: combined });
|
|
26675
26893
|
const selectedHeaders = {};
|
|
26676
|
-
for (const
|
|
26677
|
-
const value = response.headers.get(
|
|
26678
|
-
if (value) selectedHeaders[
|
|
26894
|
+
for (const name2 of ["content-type", "content-length", "location", "etag", "last-modified"]) {
|
|
26895
|
+
const value = response.headers.get(name2);
|
|
26896
|
+
if (value) selectedHeaders[name2] = value.slice(0, 2048);
|
|
26679
26897
|
}
|
|
26680
26898
|
const reader = response.body?.getReader(), chunks = [];
|
|
26681
26899
|
let count = 0, truncated = false;
|
|
@@ -26720,9 +26938,9 @@ var NativeTools = class {
|
|
|
26720
26938
|
else if (call.name === "git_diff") {
|
|
26721
26939
|
argv = ["diff", "--no-ext-diff", "--no-textconv", ...call.args.staged ? ["--cached"] : []];
|
|
26722
26940
|
if (call.args.file) {
|
|
26723
|
-
const file2 = await resolve(
|
|
26724
|
-
const relative =
|
|
26725
|
-
if (relative.startsWith(".." +
|
|
26941
|
+
const file2 = await resolve(path9.resolve(cwd, call.args.file));
|
|
26942
|
+
const relative = path9.relative(cwd, file2);
|
|
26943
|
+
if (relative.startsWith(".." + path9.sep) || relative === ".." || path9.isAbsolute(relative)) fail2("PATH_NOT_ALLOWED");
|
|
26726
26944
|
argv.push("--", relative);
|
|
26727
26945
|
} else argv.push("--stat");
|
|
26728
26946
|
} else fail2("TOOL_MISSING");
|
|
@@ -26733,9 +26951,9 @@ var NativeTools = class {
|
|
|
26733
26951
|
const listing = await this.list(guard, cwd, 1, 150, signal);
|
|
26734
26952
|
let packageInfo = null;
|
|
26735
26953
|
try {
|
|
26736
|
-
const
|
|
26737
|
-
guard.assertSafeFile(
|
|
26738
|
-
const value = JSON.parse(utf8(await this.readFile(
|
|
26954
|
+
const name2 = await guard.resolveExisting(path9.join(cwd, "package.json"));
|
|
26955
|
+
guard.assertSafeFile(name2);
|
|
26956
|
+
const value = JSON.parse(utf8(await this.readFile(name2, 64e3, signal)));
|
|
26739
26957
|
packageInfo = { name: typeof value.name === "string" ? value.name : null, version: typeof value.version === "string" ? value.version : null, scripts: value.scripts && typeof value.scripts === "object" ? Object.keys(value.scripts).slice(0, 100) : [] };
|
|
26740
26958
|
} catch (error62) {
|
|
26741
26959
|
if (error62 instanceof NativeToolError && ["FILE_READ_FAILED", "CANCELLED"].includes(error62.code)) throw error62;
|
|
@@ -26749,14 +26967,14 @@ var NativeTools = class {
|
|
|
26749
26967
|
return { ...output2, resultCode: r.exitCode === 0 ? "SUCCESS" : "GIT_FAILED" };
|
|
26750
26968
|
} };
|
|
26751
26969
|
}
|
|
26752
|
-
resolveProgram(
|
|
26753
|
-
const entry = Object.entries(this.options.programs).find(([key, value]) => key.toLowerCase() ===
|
|
26754
|
-
if (!entry || !workspace.allowedCommands.some((v) => v.toLowerCase() === entry[0].toLowerCase()) || !
|
|
26970
|
+
resolveProgram(name2, workspace) {
|
|
26971
|
+
const entry = Object.entries(this.options.programs).find(([key, value]) => key.toLowerCase() === name2.toLowerCase() || path9.isAbsolute(name2) && equalPath3(value.executable, path9.resolve(name2)));
|
|
26972
|
+
if (!entry || !workspace.allowedCommands.some((v) => v.toLowerCase() === entry[0].toLowerCase()) || !path9.isAbsolute(entry[1].executable)) fail2("COMMAND_NOT_ALLOWED");
|
|
26755
26973
|
return { executable: entry[1].executable, prefixArgs: [...entry[1].prefixArgs ?? []] };
|
|
26756
26974
|
}
|
|
26757
26975
|
gitExecution(cwd, args) {
|
|
26758
26976
|
const program = this.options.programs.git;
|
|
26759
|
-
if (!program || !
|
|
26977
|
+
if (!program || !path9.isAbsolute(program.executable)) fail2("GIT_NOT_INSTALLED");
|
|
26760
26978
|
return { executable: program.executable, args: ["--no-pager", "--no-optional-locks", "-c", "core.fsmonitor=false", "-c", "core.hooksPath=" + (process.platform === "win32" ? "NUL" : "/dev/null"), "-c", "diff.external=", ...args], cwd };
|
|
26761
26979
|
}
|
|
26762
26980
|
async list(guard, root, depth, maxEntries, signal) {
|
|
@@ -26774,7 +26992,7 @@ var NativeTools = class {
|
|
|
26774
26992
|
break;
|
|
26775
26993
|
}
|
|
26776
26994
|
if (item.isSymbolicLink() || excluded.has(item.name.toLowerCase())) continue;
|
|
26777
|
-
const candidate =
|
|
26995
|
+
const candidate = path9.join(current2.path, item.name);
|
|
26778
26996
|
let resolved;
|
|
26779
26997
|
try {
|
|
26780
26998
|
resolved = await guard.resolveExisting(candidate);
|
|
@@ -26782,13 +27000,13 @@ var NativeTools = class {
|
|
|
26782
27000
|
} catch {
|
|
26783
27001
|
continue;
|
|
26784
27002
|
}
|
|
26785
|
-
if (!
|
|
27003
|
+
if (!equalPath3(candidate, resolved)) continue;
|
|
26786
27004
|
if (item.isDirectory()) {
|
|
26787
|
-
entries.push({ path:
|
|
27005
|
+
entries.push({ path: path9.relative(root, resolved), type: "directory" });
|
|
26788
27006
|
if (current2.level < depth) queue.push({ path: resolved, level: current2.level + 1 });
|
|
26789
27007
|
} else if (item.isFile()) {
|
|
26790
|
-
const stat = await
|
|
26791
|
-
entries.push({ path:
|
|
27008
|
+
const stat = await lstat5(resolved);
|
|
27009
|
+
entries.push({ path: path9.relative(root, resolved), type: "file", size: stat.size });
|
|
26792
27010
|
}
|
|
26793
27011
|
}
|
|
26794
27012
|
if (truncated) break;
|
|
@@ -26798,49 +27016,49 @@ var NativeTools = class {
|
|
|
26798
27016
|
async gitCommonDir(cwd, workspace, guard) {
|
|
26799
27017
|
let directory = cwd;
|
|
26800
27018
|
while (guard.isWithin(directory, workspace.root)) {
|
|
26801
|
-
const dotGit =
|
|
27019
|
+
const dotGit = path9.join(directory, ".git");
|
|
26802
27020
|
try {
|
|
26803
|
-
const stat = await
|
|
27021
|
+
const stat = await lstat5(dotGit);
|
|
26804
27022
|
if (stat.isSymbolicLink()) fail2("GIT_METADATA_CHANGED");
|
|
26805
27023
|
let gitDir;
|
|
26806
|
-
if (stat.isDirectory()) gitDir = await
|
|
27024
|
+
if (stat.isDirectory()) gitDir = await realpath5(dotGit);
|
|
26807
27025
|
else {
|
|
26808
27026
|
const content = utf8(await this.readFile(dotGit, 4096)).trim();
|
|
26809
27027
|
if (!content.startsWith("gitdir: ")) fail2("GIT_METADATA_CHANGED");
|
|
26810
|
-
gitDir = await
|
|
27028
|
+
gitDir = await realpath5(path9.resolve(directory, content.slice(8)));
|
|
26811
27029
|
}
|
|
26812
27030
|
if (guard.isWithin(gitDir, this.options.stateDirectory) || !guard.isWithin(gitDir, workspace.root) && (!workspace.gitCommonDir || !guard.isWithin(gitDir, workspace.gitCommonDir))) fail2("GIT_COMMON_DIRECTORY_NOT_APPROVED");
|
|
26813
27031
|
let common = gitDir;
|
|
26814
27032
|
try {
|
|
26815
|
-
const content = utf8(await this.readFile(
|
|
27033
|
+
const content = utf8(await this.readFile(path9.join(gitDir, "commondir"), 4096)).trim();
|
|
26816
27034
|
if (!content) fail2("GIT_METADATA_CHANGED");
|
|
26817
|
-
common = await
|
|
27035
|
+
common = await realpath5(path9.resolve(gitDir, content));
|
|
26818
27036
|
} catch (e) {
|
|
26819
27037
|
if (e.code !== "ENOENT") throw e;
|
|
26820
27038
|
}
|
|
26821
|
-
if (!(await
|
|
26822
|
-
if (!guard.isWithin(common, workspace.root) && (!workspace.gitCommonDir || !
|
|
27039
|
+
if (!(await lstat5(common)).isDirectory() || guard.isWithin(common, this.options.stateDirectory)) fail2("GIT_METADATA_CHANGED");
|
|
27040
|
+
if (!guard.isWithin(common, workspace.root) && (!workspace.gitCommonDir || !equalPath3(common, workspace.gitCommonDir))) fail2("GIT_COMMON_DIRECTORY_NOT_APPROVED");
|
|
26823
27041
|
return common;
|
|
26824
27042
|
} catch (e) {
|
|
26825
27043
|
if (e.code !== "ENOENT") throw e;
|
|
26826
27044
|
}
|
|
26827
|
-
if (
|
|
26828
|
-
directory =
|
|
27045
|
+
if (equalPath3(directory, workspace.root)) break;
|
|
27046
|
+
directory = path9.dirname(directory);
|
|
26829
27047
|
}
|
|
26830
27048
|
return null;
|
|
26831
27049
|
}
|
|
26832
27050
|
};
|
|
26833
27051
|
|
|
26834
27052
|
// agent/src/scheduler.ts
|
|
26835
|
-
import
|
|
27053
|
+
import path10 from "node:path";
|
|
26836
27054
|
import os2 from "node:os";
|
|
26837
27055
|
function canonical(value) {
|
|
26838
|
-
const p =
|
|
27056
|
+
const p = path10.resolve(value);
|
|
26839
27057
|
return process.platform === "win32" ? p.toLowerCase() : p;
|
|
26840
27058
|
}
|
|
26841
27059
|
function ancestor(a, b) {
|
|
26842
|
-
const relative =
|
|
26843
|
-
return relative === "" || !
|
|
27060
|
+
const relative = path10.relative(a, b);
|
|
27061
|
+
return relative === "" || !path10.isAbsolute(relative) && relative !== ".." && !relative.startsWith(".." + path10.sep);
|
|
26844
27062
|
}
|
|
26845
27063
|
function claimsConflict(a, b) {
|
|
26846
27064
|
return a.some((x) => b.some((y) => (x.namespace ?? "filesystem") === (y.namespace ?? "filesystem") && (x.mode === "write" || y.mode === "write") && (ancestor(x.path, y.path) || ancestor(y.path, x.path))));
|
|
@@ -26860,7 +27078,7 @@ var NativeScheduler = class {
|
|
|
26860
27078
|
schedule(id, claims, deadlineAt, run, signal) {
|
|
26861
27079
|
if (!this.accepting) return Promise.reject(Error("AGENT_PAUSED"));
|
|
26862
27080
|
if (!id || id.length > 100 || this.active.has(id) || this.waiting.some((j) => j.id === id)) return Promise.reject(Error("DUPLICATE_SCHEDULED_EXECUTION"));
|
|
26863
|
-
if (!Array.isArray(claims) || !claims.length || claims.length > 32 || claims.some((c) => !c || !
|
|
27081
|
+
if (!Array.isArray(claims) || !claims.length || claims.length > 32 || claims.some((c) => !c || !path10.isAbsolute(c.path) || !["read", "write"].includes(c.mode))) return Promise.reject(Error("INVALID_RESOURCE_CLAIM"));
|
|
26864
27082
|
const deadline = deadlineAt.getTime();
|
|
26865
27083
|
if (!Number.isFinite(deadline) || deadline <= Date.now()) return Promise.reject(Error("ADMISSION_DEADLINE_EXPIRED"));
|
|
26866
27084
|
if (signal?.aborted) return Promise.reject(Error("CANCELLED_BEFORE_ACCEPTANCE"));
|
|
@@ -27320,17 +27538,17 @@ function unshapedResultTelemetry(result2) {
|
|
|
27320
27538
|
|
|
27321
27539
|
// packages/optimization/src/live-telemetry.ts
|
|
27322
27540
|
var LIVE_EFFICIENCY_EVENT_VERSION = 1;
|
|
27323
|
-
var
|
|
27541
|
+
var uuid15 = external_exports.string().uuid().toLowerCase();
|
|
27324
27542
|
var bytes2 = external_exports.number().int().min(0).max(1e9);
|
|
27325
27543
|
var utc4 = external_exports.iso.datetime({ offset: false });
|
|
27326
27544
|
var LiveEfficiencyEventSchema = external_exports.object({
|
|
27327
27545
|
version: external_exports.literal(LIVE_EFFICIENCY_EVENT_VERSION),
|
|
27328
27546
|
occurredAt: utc4,
|
|
27329
|
-
tenantId:
|
|
27330
|
-
accountId:
|
|
27331
|
-
sessionId:
|
|
27332
|
-
operationId:
|
|
27333
|
-
executionId:
|
|
27547
|
+
tenantId: uuid15,
|
|
27548
|
+
accountId: uuid15.nullable(),
|
|
27549
|
+
sessionId: uuid15,
|
|
27550
|
+
operationId: uuid15,
|
|
27551
|
+
executionId: uuid15,
|
|
27334
27552
|
toolName: external_exports.string().trim().min(1).max(120),
|
|
27335
27553
|
outcome: external_exports.enum(["success", "failed", "timed_out", "cancelled", "unknown"]),
|
|
27336
27554
|
parserId: external_exports.string().trim().min(1).max(120).nullable(),
|
|
@@ -27812,10 +28030,10 @@ var NativeRelayBridge = class {
|
|
|
27812
28030
|
const { binding: binding2, tool } = parsed2.data;
|
|
27813
28031
|
this.binding(request, binding2);
|
|
27814
28032
|
const prepared = await this.wait(this.execution, binding2.executionId, request, () => this.runtime.receive({ type: "prepare", version: 1, binding: binding2, tool }));
|
|
27815
|
-
if (prepared.type === "not_accepted") return { accepted: false, provedNotStarted: prepared.provedNotStarted };
|
|
28033
|
+
if (prepared.type === "not_accepted") return { accepted: false, provedNotStarted: prepared.provedNotStarted, code: prepared.code };
|
|
27816
28034
|
if (prepared.type !== "prepared") throw failure3("INFRA_UNAVAILABLE");
|
|
27817
28035
|
const accepted = await this.wait(this.execution, binding2.executionId, request, () => this.runtime.receive({ type: "execute", version: 1, binding: binding2 }));
|
|
27818
|
-
if (accepted.type === "not_accepted") return { accepted: false, provedNotStarted: accepted.provedNotStarted };
|
|
28036
|
+
if (accepted.type === "not_accepted") return { accepted: false, provedNotStarted: accepted.provedNotStarted, code: accepted.code };
|
|
27819
28037
|
if (accepted.type !== "accepted") throw failure3("INFRA_UNAVAILABLE");
|
|
27820
28038
|
return { accepted: true };
|
|
27821
28039
|
}
|
|
@@ -27878,7 +28096,7 @@ function relayUrlFromEnrollment(enrollment) {
|
|
|
27878
28096
|
url2.pathname = "/api/agent/relay";
|
|
27879
28097
|
return url2.toString();
|
|
27880
28098
|
}
|
|
27881
|
-
function
|
|
28099
|
+
function uuid16(value, code) {
|
|
27882
28100
|
if (typeof value !== "string" || !UUID2.test(value)) fail3(code);
|
|
27883
28101
|
return value.toLowerCase();
|
|
27884
28102
|
}
|
|
@@ -28008,37 +28226,37 @@ function sendJson(socket, value, maxBytes) {
|
|
|
28008
28226
|
function validateChallenge(frame, expectedProtocol) {
|
|
28009
28227
|
if (frame.type !== "challenge") fail3("RELAY_HANDSHAKE_INVALID");
|
|
28010
28228
|
protocol(frame.protocolVersion, expectedProtocol);
|
|
28011
|
-
|
|
28229
|
+
uuid16(frame.gatewayId, "RELAY_HANDSHAKE_INVALID");
|
|
28012
28230
|
if (typeof frame.nonce !== "string" || frame.nonce.length < 32 || frame.nonce.length > 512 || /[\u0000-\u001f\u007f]/u.test(frame.nonce)) fail3("RELAY_HANDSHAKE_INVALID");
|
|
28013
28231
|
if (typeof frame.expiresAt !== "string" || !Number.isFinite(Date.parse(frame.expiresAt)) || Date.parse(frame.expiresAt) <= Date.now()) fail3("RELAY_HANDSHAKE_INVALID");
|
|
28014
28232
|
return {
|
|
28015
|
-
challengeId:
|
|
28233
|
+
challengeId: uuid16(frame.challengeId, "RELAY_HANDSHAKE_INVALID"),
|
|
28016
28234
|
nonce: frame.nonce
|
|
28017
28235
|
};
|
|
28018
28236
|
}
|
|
28019
28237
|
function validateReady(frame, enrollment, expectedProtocol) {
|
|
28020
28238
|
if (frame.type !== "connected") fail3("RELAY_HANDSHAKE_INVALID");
|
|
28021
28239
|
protocol(frame.protocolVersion, expectedProtocol);
|
|
28022
|
-
if (
|
|
28023
|
-
if (
|
|
28240
|
+
if (uuid16(frame.tenantId, "RELAY_HANDSHAKE_INVALID") !== enrollment.tenantId.toLowerCase()) fail3("RELAY_ID_MISMATCH");
|
|
28241
|
+
if (uuid16(frame.deviceId, "RELAY_HANDSHAKE_INVALID") !== enrollment.deviceId.toLowerCase()) fail3("RELAY_ID_MISMATCH");
|
|
28024
28242
|
return epoch2(frame.connectionEpoch);
|
|
28025
28243
|
}
|
|
28026
28244
|
function validateRequest(frame, enrollment, expectedProtocol, connectionEpoch) {
|
|
28027
28245
|
if (frame.type !== "relayFrame") fail3("RELAY_REQUEST_INVALID");
|
|
28028
28246
|
protocol(frame.protocolVersion, expectedProtocol);
|
|
28029
|
-
const tenantId =
|
|
28030
|
-
const deviceId =
|
|
28247
|
+
const tenantId = uuid16(frame.tenantId, "RELAY_REQUEST_INVALID");
|
|
28248
|
+
const deviceId = uuid16(frame.deviceId, "RELAY_REQUEST_INVALID");
|
|
28031
28249
|
if (tenantId !== enrollment.tenantId.toLowerCase() || deviceId !== enrollment.deviceId.toLowerCase()) fail3("RELAY_ID_MISMATCH");
|
|
28032
28250
|
if (epoch2(frame.connectionEpoch) !== connectionEpoch) fail3("RELAY_EPOCH_MISMATCH");
|
|
28033
28251
|
const kind = frame.kind;
|
|
28034
28252
|
if (typeof kind !== "string" || !REQUEST_KINDS.has(kind)) fail3("RELAY_REQUEST_INVALID");
|
|
28035
28253
|
return {
|
|
28036
|
-
requestId:
|
|
28254
|
+
requestId: uuid16(frame.requestId, "RELAY_REQUEST_INVALID"),
|
|
28037
28255
|
tenantId,
|
|
28038
28256
|
deviceId,
|
|
28039
28257
|
connectionEpoch,
|
|
28040
|
-
operationId:
|
|
28041
|
-
executionId:
|
|
28258
|
+
operationId: uuid16(frame.operationId, "RELAY_REQUEST_INVALID"),
|
|
28259
|
+
executionId: uuid16(frame.executionId, "RELAY_REQUEST_INVALID"),
|
|
28042
28260
|
kind,
|
|
28043
28261
|
payload: frame.payload,
|
|
28044
28262
|
deadlineAt: isoDate(frame.deadlineAt)
|
|
@@ -28078,8 +28296,8 @@ async function connectOnce(options) {
|
|
|
28078
28296
|
const identity = await options.store.load();
|
|
28079
28297
|
if (!identity || !identity.enrollment) fail3("ENROLLMENT_MISSING");
|
|
28080
28298
|
const enrollment = identity.enrollment;
|
|
28081
|
-
const tenantId =
|
|
28082
|
-
const deviceId =
|
|
28299
|
+
const tenantId = uuid16(enrollment.tenantId, "ENROLLMENT_INVALID");
|
|
28300
|
+
const deviceId = uuid16(enrollment.deviceId, "ENROLLMENT_INVALID");
|
|
28083
28301
|
const url2 = relayUrlFromEnrollment(enrollment);
|
|
28084
28302
|
const socket = new wrapper_default(url2, [], {
|
|
28085
28303
|
followRedirects: false,
|
|
@@ -28299,10 +28517,10 @@ function canonicalOrigin2(value) {
|
|
|
28299
28517
|
fail4("ORIGIN_INVALID");
|
|
28300
28518
|
}
|
|
28301
28519
|
}
|
|
28302
|
-
function endpoint(origin,
|
|
28303
|
-
return origin +
|
|
28520
|
+
function endpoint(origin, path13) {
|
|
28521
|
+
return origin + path13;
|
|
28304
28522
|
}
|
|
28305
|
-
function
|
|
28523
|
+
function uuid17(value, code) {
|
|
28306
28524
|
if (typeof value !== "string" || !UUID3.test(value)) fail4(code);
|
|
28307
28525
|
return value.toLowerCase();
|
|
28308
28526
|
}
|
|
@@ -28422,7 +28640,7 @@ var PairingClient = class {
|
|
|
28422
28640
|
publicKey: identity.publicKeyPem,
|
|
28423
28641
|
agentVersion: this.version
|
|
28424
28642
|
}));
|
|
28425
|
-
const pairingId =
|
|
28643
|
+
const pairingId = uuid17(response.pairingId, "PAIRING_RESPONSE_INVALID");
|
|
28426
28644
|
const expiresAt = parseDate(response.expiresAt, "PAIRING_RESPONSE_INVALID");
|
|
28427
28645
|
const now = this.nowMs();
|
|
28428
28646
|
if (expiresAt.getTime() <= now || expiresAt.getTime() - now > 30 * 6e4) fail4("PAIRING_RESPONSE_INVALID");
|
|
@@ -28456,9 +28674,9 @@ var PairingClient = class {
|
|
|
28456
28674
|
}
|
|
28457
28675
|
async completeOnce(beginResult, approved, displayNameValue) {
|
|
28458
28676
|
const pairing = this.validateBeginResult(beginResult);
|
|
28459
|
-
const tenantId =
|
|
28677
|
+
const tenantId = uuid17(approved.tenantId, "PAIRING_RESPONSE_INVALID");
|
|
28460
28678
|
const email3 = accountEmail(approved.accountEmail);
|
|
28461
|
-
const
|
|
28679
|
+
const name2 = displayName(displayNameValue);
|
|
28462
28680
|
const identity = await this.currentIdentity();
|
|
28463
28681
|
const keyFingerprint = fingerprint2(identity.fingerprint);
|
|
28464
28682
|
if (keyFingerprint !== pairing.keyFingerprint) fail4("IDENTITY_CHANGED");
|
|
@@ -28467,18 +28685,18 @@ var PairingClient = class {
|
|
|
28467
28685
|
const response = assertJsonObject(await this.request(endpoint(this.origin, "/api/agent/pairings/" + pairing.pairingId + "/complete"), {
|
|
28468
28686
|
pairingSecret: pairing.pairingSecret,
|
|
28469
28687
|
signature,
|
|
28470
|
-
displayName:
|
|
28688
|
+
displayName: name2,
|
|
28471
28689
|
agentVersion: this.version
|
|
28472
28690
|
}));
|
|
28473
|
-
const completedTenant =
|
|
28691
|
+
const completedTenant = uuid17(response.tenantId, "PAIRING_RESPONSE_INVALID");
|
|
28474
28692
|
if (completedTenant !== tenantId) fail4("PAIRING_RESPONSE_INVALID");
|
|
28475
|
-
const deviceId =
|
|
28693
|
+
const deviceId = uuid17(response.deviceId, "PAIRING_RESPONSE_INVALID");
|
|
28476
28694
|
const enrollment = {
|
|
28477
28695
|
tenantId,
|
|
28478
28696
|
deviceId,
|
|
28479
28697
|
serverOrigin: this.origin,
|
|
28480
28698
|
accountEmail: email3,
|
|
28481
|
-
...response.accountId !== void 0 ? { accountId:
|
|
28699
|
+
...response.accountId !== void 0 ? { accountId: uuid17(response.accountId, "PAIRING_RESPONSE_INVALID") } : {}
|
|
28482
28700
|
};
|
|
28483
28701
|
await this.store.saveEnrollment(enrollment);
|
|
28484
28702
|
return { ...enrollment };
|
|
@@ -28492,7 +28710,7 @@ var PairingClient = class {
|
|
|
28492
28710
|
state: response.state,
|
|
28493
28711
|
expiresAt: parseDate(response.expiresAt, "PAIRING_RESPONSE_INVALID")
|
|
28494
28712
|
};
|
|
28495
|
-
if (response.tenantId !== void 0) result2.tenantId =
|
|
28713
|
+
if (response.tenantId !== void 0) result2.tenantId = uuid17(response.tenantId, "PAIRING_RESPONSE_INVALID");
|
|
28496
28714
|
if (response.accountEmail !== void 0) result2.accountEmail = accountEmail(response.accountEmail);
|
|
28497
28715
|
return result2;
|
|
28498
28716
|
}
|
|
@@ -28504,10 +28722,10 @@ var PairingClient = class {
|
|
|
28504
28722
|
}
|
|
28505
28723
|
validateBeginResult(value) {
|
|
28506
28724
|
return {
|
|
28507
|
-
pairingId:
|
|
28725
|
+
pairingId: uuid17(value.pairingId, "PAIRING_INVALID"),
|
|
28508
28726
|
pairingSecret: pairingSecret(value.pairingSecret),
|
|
28509
28727
|
confirmationCode: confirmationCode(value.confirmationCode),
|
|
28510
|
-
approvalUrl: approvalUrl(value.approvalUrl, this.origin,
|
|
28728
|
+
approvalUrl: approvalUrl(value.approvalUrl, this.origin, uuid17(value.pairingId, "PAIRING_INVALID")),
|
|
28511
28729
|
expiresAt: value.expiresAt instanceof Date ? new Date(value.expiresAt.getTime()) : parseDate(value.expiresAt, "PAIRING_INVALID"),
|
|
28512
28730
|
keyFingerprint: fingerprint2(value.keyFingerprint)
|
|
28513
28731
|
};
|
|
@@ -28802,30 +29020,30 @@ async function deliverGuardEvidence(target, packet, options = {}) {
|
|
|
28802
29020
|
|
|
28803
29021
|
// agent/src/optimization-ledger.ts
|
|
28804
29022
|
import { constants as constants4 } from "node:fs";
|
|
28805
|
-
import { lstat as
|
|
28806
|
-
import
|
|
29023
|
+
import { lstat as lstat6, mkdir as mkdir4, open as open5, readFile as readFile4, realpath as realpath6 } from "node:fs/promises";
|
|
29024
|
+
import path11 from "node:path";
|
|
28807
29025
|
var MAX_LEDGER_BYTES = 64 * 1024 * 1024;
|
|
28808
29026
|
var MAX_NOTICE_LEDGER_BYTES = 8 * 1024 * 1024;
|
|
28809
29027
|
function failure4(code) {
|
|
28810
29028
|
return Object.assign(new Error(code), { code });
|
|
28811
29029
|
}
|
|
28812
29030
|
async function safeDirectory2(directory) {
|
|
28813
|
-
const absolute2 =
|
|
29031
|
+
const absolute2 = path11.resolve(directory);
|
|
28814
29032
|
let current2 = absolute2;
|
|
28815
29033
|
for (; ; ) {
|
|
28816
29034
|
try {
|
|
28817
|
-
if ((await
|
|
29035
|
+
if ((await lstat6(current2)).isSymbolicLink()) throw failure4("OPTIMIZATION_LEDGER_DIRECTORY_INVALID");
|
|
28818
29036
|
} catch (error62) {
|
|
28819
29037
|
if (error62.code !== "ENOENT") throw error62;
|
|
28820
29038
|
}
|
|
28821
|
-
const parent =
|
|
29039
|
+
const parent = path11.dirname(current2);
|
|
28822
29040
|
if (parent === current2) break;
|
|
28823
29041
|
current2 = parent;
|
|
28824
29042
|
}
|
|
28825
29043
|
await mkdir4(absolute2, { recursive: true, mode: 448 });
|
|
28826
|
-
const stat = await
|
|
29044
|
+
const stat = await lstat6(absolute2);
|
|
28827
29045
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw failure4("OPTIMIZATION_LEDGER_DIRECTORY_INVALID");
|
|
28828
|
-
return
|
|
29046
|
+
return realpath6(absolute2);
|
|
28829
29047
|
}
|
|
28830
29048
|
var OptimizationLedger = class _OptimizationLedger {
|
|
28831
29049
|
constructor(filename, handle, noticesFilename) {
|
|
@@ -28843,11 +29061,11 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
28843
29061
|
closed = false;
|
|
28844
29062
|
static async open(directory) {
|
|
28845
29063
|
const dir = await safeDirectory2(directory);
|
|
28846
|
-
const filename =
|
|
28847
|
-
const noticesFilename =
|
|
29064
|
+
const filename = path11.join(dir, "live-efficiency.v1.jsonl");
|
|
29065
|
+
const noticesFilename = path11.join(dir, "live-efficiency-notices.v1.jsonl");
|
|
28848
29066
|
let existing = "";
|
|
28849
29067
|
try {
|
|
28850
|
-
const stat = await
|
|
29068
|
+
const stat = await lstat6(filename);
|
|
28851
29069
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_LEDGER_BYTES) {
|
|
28852
29070
|
throw failure4("OPTIMIZATION_LEDGER_INVALID");
|
|
28853
29071
|
}
|
|
@@ -28863,7 +29081,7 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
28863
29081
|
const ledger = new _OptimizationLedger(filename, handle, noticesFilename);
|
|
28864
29082
|
try {
|
|
28865
29083
|
try {
|
|
28866
|
-
const noticeStat = await
|
|
29084
|
+
const noticeStat = await lstat6(noticesFilename);
|
|
28867
29085
|
if (!noticeStat.isFile() || noticeStat.isSymbolicLink() || noticeStat.size > MAX_NOTICE_LEDGER_BYTES) throw failure4("OPTIMIZATION_NOTICE_LEDGER_INVALID");
|
|
28868
29086
|
const notices = await readFile4(noticesFilename, "utf8");
|
|
28869
29087
|
for (const line of notices.split("\n")) {
|
|
@@ -28930,7 +29148,7 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
28930
29148
|
const marker = JSON.stringify({ sessionId: event.sessionId, checkpointCalls, deliveredAt: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
|
|
28931
29149
|
let currentBytes = 0;
|
|
28932
29150
|
try {
|
|
28933
|
-
const stat = await
|
|
29151
|
+
const stat = await lstat6(this.noticesFilename);
|
|
28934
29152
|
if (!stat.isFile() || stat.isSymbolicLink()) throw failure4("OPTIMIZATION_NOTICE_LEDGER_INVALID");
|
|
28935
29153
|
currentBytes = stat.size;
|
|
28936
29154
|
} catch (error62) {
|
|
@@ -28977,7 +29195,7 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
28977
29195
|
};
|
|
28978
29196
|
|
|
28979
29197
|
// agent/src/main.ts
|
|
28980
|
-
var AGENT_VERSION2 = true ? "0.3.
|
|
29198
|
+
var AGENT_VERSION2 = true ? "0.3.4" : "0.3.2";
|
|
28981
29199
|
function processAlive(pid) {
|
|
28982
29200
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
28983
29201
|
try {
|
|
@@ -28989,7 +29207,7 @@ function processAlive(pid) {
|
|
|
28989
29207
|
}
|
|
28990
29208
|
async function claimAgentProcess(stateDir, runtimeDir, version2) {
|
|
28991
29209
|
await mkdir5(stateDir, { recursive: true });
|
|
28992
|
-
const file2 =
|
|
29210
|
+
const file2 = path12.join(stateDir, "agent-process.json");
|
|
28993
29211
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
28994
29212
|
try {
|
|
28995
29213
|
const handle = await open6(file2, "wx", 384);
|
|
@@ -29100,10 +29318,10 @@ async function runAgent(options) {
|
|
|
29100
29318
|
async function main() {
|
|
29101
29319
|
if (process.platform !== "win32") throw Error("WINDOWS_REQUIRED");
|
|
29102
29320
|
const appData = process.env.LOCALAPPDATA;
|
|
29103
|
-
if (!appData || !
|
|
29104
|
-
const stateDir = process.env.LC_AGENT_STATE_DIR ??
|
|
29105
|
-
const binaryDir =
|
|
29106
|
-
const ownerBridgePath = process.env.LC_OWNER_BRIDGE ??
|
|
29321
|
+
if (!appData || !path12.isAbsolute(appData)) throw Error("APP_DATA_MISSING");
|
|
29322
|
+
const stateDir = process.env.LC_AGENT_STATE_DIR ?? path12.join(appData, "LocalCommander", "Agent");
|
|
29323
|
+
const binaryDir = path12.dirname(process.execPath);
|
|
29324
|
+
const ownerBridgePath = process.env.LC_OWNER_BRIDGE ?? path12.join(binaryDir, "OwnerBridge.exe");
|
|
29107
29325
|
const store = await WindowsPrivateStore.open(stateDir, ownerBridgePath);
|
|
29108
29326
|
const command = process.argv[2] ?? "run";
|
|
29109
29327
|
if (command === "pair") {
|
|
@@ -29125,18 +29343,33 @@ async function main() {
|
|
|
29125
29343
|
}) + "\n");
|
|
29126
29344
|
return;
|
|
29127
29345
|
}
|
|
29346
|
+
if (command === "configure-access") {
|
|
29347
|
+
if (process.argv.length !== 3) throw Error("AGENT_COMMAND_INVALID");
|
|
29348
|
+
process.stdout.write(await store.configureAccess() ? "ACCESS_CONFIGURED\n" : "ACCESS_CANCELLED\n");
|
|
29349
|
+
return;
|
|
29350
|
+
}
|
|
29128
29351
|
if (command === "approve-workspace") {
|
|
29129
29352
|
process.stdout.write(await store.approveWorkspace() ? "WORKSPACE_APPROVED\n" : "WORKSPACE_CANCELLED\n");
|
|
29130
29353
|
return;
|
|
29131
29354
|
}
|
|
29132
29355
|
if (command === "list-workspaces") {
|
|
29133
|
-
|
|
29356
|
+
const programs = await store.loadPrograms(), history = await store.loadPermissionHistory();
|
|
29357
|
+
process.stdout.write(JSON.stringify((await store.loadWorkspaces()).map((w) => ({
|
|
29358
|
+
workspaceGrantId: w.workspaceGrantId,
|
|
29359
|
+
displayName: w.displayName,
|
|
29360
|
+
path: w.root,
|
|
29361
|
+
permissions: w.permissions,
|
|
29362
|
+
allowedCommands: w.allowedCommands,
|
|
29363
|
+
executables: Object.fromEntries(w.allowedCommands.map((name2) => [name2, programs[name2]?.executable ?? null])),
|
|
29364
|
+
localhostPorts: w.localhostPorts,
|
|
29365
|
+
permissionHistory: history.filter((entry) => entry.workspaceGrantId === w.workspaceGrantId)
|
|
29366
|
+
}))) + "\n");
|
|
29134
29367
|
return;
|
|
29135
29368
|
}
|
|
29136
29369
|
const args = process.argv.slice(3);
|
|
29137
29370
|
if (command === "efficiency-report") {
|
|
29138
29371
|
if (args.length) throw Error("AGENT_COMMAND_INVALID");
|
|
29139
|
-
const ledger = await OptimizationLedger.open(
|
|
29372
|
+
const ledger = await OptimizationLedger.open(path12.join(stateDir, "optimization"));
|
|
29140
29373
|
try {
|
|
29141
29374
|
process.stdout.write(JSON.stringify(await ledger.report(), null, 2) + "\n");
|
|
29142
29375
|
} finally {
|
|
@@ -29146,7 +29379,7 @@ async function main() {
|
|
|
29146
29379
|
}
|
|
29147
29380
|
if (command === "guard-configure") {
|
|
29148
29381
|
if (args.length !== 1) throw Error("AGENT_COMMAND_INVALID");
|
|
29149
|
-
const filename = await (await import("node:fs/promises")).realpath(
|
|
29382
|
+
const filename = await (await import("node:fs/promises")).realpath(path12.resolve(args[0]));
|
|
29150
29383
|
const input2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readWindowsFile(ownerBridgePath, filename, 2e5)));
|
|
29151
29384
|
if (!input2 || typeof input2 !== "object" || Object.keys(input2).sort().join(",") !== "compact,settings") throw Error("GUARD_CONFIGURATION_INVALID");
|
|
29152
29385
|
await store.configureGuard(input2.settings, input2.compact);
|
|
@@ -29165,7 +29398,7 @@ async function main() {
|
|
|
29165
29398
|
}
|
|
29166
29399
|
if (command === "guard-evidence") {
|
|
29167
29400
|
if (args.length) throw Error("AGENT_COMMAND_INVALID");
|
|
29168
|
-
const journal = await NativeJournal.open(
|
|
29401
|
+
const journal = await NativeJournal.open(path12.join(stateDir, "journal"), new SecretBox({ "1": await store.loadDataKey() }));
|
|
29169
29402
|
try {
|
|
29170
29403
|
process.stdout.write(JSON.stringify(await journal.guardEvidence()) + "\n");
|
|
29171
29404
|
} finally {
|
|
@@ -29177,7 +29410,7 @@ async function main() {
|
|
|
29177
29410
|
if (args.length !== (command === "guard-export" ? 0 : 1)) throw Error("AGENT_COMMAND_INVALID");
|
|
29178
29411
|
const identity = await store.load();
|
|
29179
29412
|
if (!identity?.enrollment) throw Error("ENROLLMENT_MISSING");
|
|
29180
|
-
const journal = await NativeJournal.open(
|
|
29413
|
+
const journal = await NativeJournal.open(path12.join(stateDir, "journal"), new SecretBox({ "1": await store.loadDataKey() }));
|
|
29181
29414
|
try {
|
|
29182
29415
|
const { tenantId, deviceId } = identity.enrollment;
|
|
29183
29416
|
const records = (await journal.guardEvidence()).filter((record2) => record2.tenantId === tenantId && record2.deviceId === deviceId);
|
|
@@ -29216,10 +29449,10 @@ async function main() {
|
|
|
29216
29449
|
if (!(await store.load())?.enrollment) throw Error("ENROLLMENT_MISSING");
|
|
29217
29450
|
const releaseProcess = await claimAgentProcess(stateDir, binaryDir, AGENT_VERSION2);
|
|
29218
29451
|
try {
|
|
29219
|
-
const journal = await NativeJournal.open(
|
|
29220
|
-
const efficiencyLedger = await OptimizationLedger.open(
|
|
29452
|
+
const journal = await NativeJournal.open(path12.join(stateDir, "journal"), new SecretBox({ "1": await store.loadDataKey() }));
|
|
29453
|
+
const efficiencyLedger = await OptimizationLedger.open(path12.join(stateDir, "optimization"));
|
|
29221
29454
|
try {
|
|
29222
|
-
const tools = new NativeTools({ stateDirectory: stateDir, helperPath: process.env.LC_JOB_HOST ??
|
|
29455
|
+
const tools = new NativeTools({ stateDirectory: stateDir, helperPath: process.env.LC_JOB_HOST ?? path12.join(binaryDir, "JobHost.exe"), ownerBridgePath, programs: await store.loadPrograms() });
|
|
29223
29456
|
const stop = new AbortController();
|
|
29224
29457
|
process.once("SIGINT", () => stop.abort());
|
|
29225
29458
|
process.once("SIGTERM", () => stop.abort());
|
|
@@ -29233,7 +29466,7 @@ async function main() {
|
|
|
29233
29466
|
await releaseProcess();
|
|
29234
29467
|
}
|
|
29235
29468
|
}
|
|
29236
|
-
if (process.argv[1] &&
|
|
29469
|
+
if (process.argv[1] && path12.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
29237
29470
|
void main().catch((error62) => {
|
|
29238
29471
|
const code = error62 instanceof Error && /^[A-Z0-9_]{1,64}$/.test(error62.message) ? error62.message : "AGENT_START_FAILED";
|
|
29239
29472
|
process.stderr.write(code + "\n");
|