openfox 2.0.74 → 2.0.76
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 +5 -2
- package/dist/{chat-handler-ETKFIHUO.js → chat-handler-3BZ4PDL2.js} +17 -16
- package/dist/chunk-46RORSKU.js +495 -0
- package/dist/{chunk-S7O77CVO.js → chunk-4QHDQP2N.js} +2 -2
- package/dist/{chunk-YD6NDTKF.js → chunk-5GOOSZX7.js} +1 -1
- package/dist/{chunk-XWBNYU2L.js → chunk-7772ENZN.js} +4 -3
- package/dist/{chunk-I4RWK2L2.js → chunk-7QXRQ5ZL.js} +3 -3
- package/dist/{chunk-2VBETAU2.js → chunk-D6CI2B7B.js} +111 -539
- package/dist/{chunk-E5LLEO3C.js → chunk-DH3SSBZN.js} +2 -2
- package/dist/{chunk-CHPRGEY5.js → chunk-HAQV6AJP.js} +3 -3
- package/dist/{chunk-R6L2QXQ5.js → chunk-HHUIXHHX.js} +16 -6
- package/dist/{chunk-ZRQDCQ7R.js → chunk-HLHGNHMV.js} +10 -8
- package/dist/{chunk-QFR6JF32.js → chunk-J7VCI5EX.js} +205 -82
- package/dist/chunk-KQEVAO2Q.js +7 -0
- package/dist/{chunk-UN26GIQG.js → chunk-KVRSAC4Z.js} +1 -1
- package/dist/{chunk-UV3EMWL4.js → chunk-LXFLRM7M.js} +2 -2
- package/dist/{chunk-6XKM25OT.js → chunk-NGJ5XJ7D.js} +4 -4
- package/dist/{chunk-NOU2QIL5.js → chunk-O3NUNU4H.js} +5 -2
- package/dist/{chunk-PYB2UEC3.js → chunk-OBQPXUXI.js} +4 -4
- package/dist/{chunk-OHGQILRF.js → chunk-OCVRIPQP.js} +2 -2
- package/dist/{chunk-TMRFTWXP.js → chunk-OMWVB2XC.js} +38 -23
- package/dist/{chunk-6XOA2WPU.js → chunk-W4ZEG3LX.js} +5 -2
- package/dist/{chunk-2DCJERYX.js → chunk-XTK6E2QR.js} +4 -4
- package/dist/{chunk-QNKXZH3Z.js → chunk-YMFCFMMB.js} +2 -2
- package/dist/cli/dev.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/{client-SC6X65GY.js → client-TDPQVBQ7.js} +2 -2
- package/dist/{compactor-4UKYWC5F.js → compactor-BB3YYVRP.js} +7 -7
- package/dist/{events-AEQ6GQXI.js → events-FLWRLM6V.js} +3 -3
- package/dist/{inspect-proxy-PHVZBZUO.js → inspect-proxy-JHZV34QR.js} +4 -4
- package/dist/install-CPOPMCSE.js +120 -0
- package/dist/{manager-24C2AXVF.js → manager-OPMCSYN2.js} +5 -5
- package/dist/{orchestrator-O2YAAZ3U.js → orchestrator-5IMCWX5P.js} +16 -15
- package/dist/package.json +1 -1
- package/dist/path-security-ZROWNL22.js +50 -0
- package/dist/{platform-XQ7BS3KQ.js → platform-QGXO3CNR.js} +4 -4
- package/dist/plugins-registry.json +6 -0
- package/dist/{processor-FKAS3K4L.js → processor-3R4GS6VB.js} +17 -16
- package/dist/{project-creator-FQRBN3RG.js → project-creator-C22J43BC.js} +3 -3
- package/dist/{projects-LHRNCUGW.js → projects-WSVINK2C.js} +3 -3
- package/dist/{protocol-4QFU7WAI.js → protocol-EWEVW65B.js} +3 -3
- package/dist/{protocol-DGQWVFcP.d.ts → protocol-Sr6Jr4ZS.d.ts} +5 -2
- package/dist/{provider-OJUCZ5RN.js → provider-G2YC2IC4.js} +3 -3
- package/dist/{provider-manager-MAKXSL6L.js → provider-manager-SQIELYLK.js} +3 -3
- package/dist/{serve-T5U6SLB7.js → serve-MXKMKZYY.js} +22 -21
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +21 -20
- package/dist/{server-T2YP3VT2.js → server-SPYFMFQE.js} +18 -17
- package/dist/{settings-PFXRYXZU.js → settings-7XDMSPIC.js} +3 -3
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/{tools-APW7MY4E.js → tools-JRCUWPJO.js} +20 -18
- package/dist/{update-FWO365SV.js → update-OXDQ75M7.js} +12 -3
- package/dist/{update-server-MYYQUNZX.js → update-server-4MTM7M5P.js} +2 -2
- package/dist/web/assets/{index-DZRcUhoE.css → index-BNVBxKnB.css} +1 -1
- package/dist/web/assets/{index-Xh2VHbZe.js → index-ghDkIdlf.js} +74 -74
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/plugins-registry.json +6 -0
- package/dist/chunk-3OZBPWHP.js +0 -7
package/README.md
CHANGED
|
@@ -85,6 +85,8 @@ openfox provider remove # Remove provider
|
|
|
85
85
|
|
|
86
86
|
OpenFox supports provider plugins loaded from the `plugins` directory inside its configuration folder. Plugins can add authentication methods, transports, provider presets, and model discovery without adding provider-specific code to the OpenFox core.
|
|
87
87
|
|
|
88
|
+
You can install the plugin manually or directly from the UI.
|
|
89
|
+
|
|
88
90
|
Default plugin directories:
|
|
89
91
|
|
|
90
92
|
- macOS: `~/Library/Application Support/openfox/plugins`
|
|
@@ -93,8 +95,9 @@ Default plugin directories:
|
|
|
93
95
|
|
|
94
96
|
Restart OpenFox after installing or updating a plugin.
|
|
95
97
|
|
|
96
|
-
To authenticate with a ChatGPT Plus or Pro account, you can install the [`openfox-chatgpt`](https://github.com/arthurlacoste/openfox-chatgpt) plugin.
|
|
97
|
-
To authenticate with a Github copilot account, you can install the [`openfox-github-copilot`](https://github.com/JamesDAdams/openfox-github-copilot) plugin.
|
|
98
|
+
- To authenticate with a ChatGPT Plus or Pro account, you can install the [`openfox-chatgpt`](https://github.com/arthurlacoste/openfox-chatgpt) plugin.
|
|
99
|
+
- To authenticate with a Github copilot account, you can install the [`openfox-github-copilot`](https://github.com/JamesDAdams/openfox-github-copilot) plugin.
|
|
100
|
+
- To authenticate with a Google Antigravity account, you can install the [`openfox-google-antigravity`](https://github.com/JamesDAdams/openfox-google-antigravity) plugin.
|
|
98
101
|
|
|
99
102
|
## Screenshots
|
|
100
103
|
|
|
@@ -2,39 +2,40 @@ import {
|
|
|
2
2
|
buildRunChatTurnParams,
|
|
3
3
|
finalizeTurnCompletion,
|
|
4
4
|
generateSessionNameForSession
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XTK6E2QR.js";
|
|
6
6
|
import {
|
|
7
7
|
runChatTurn
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-HLHGNHMV.js";
|
|
9
|
+
import "./chunk-D6CI2B7B.js";
|
|
10
|
+
import "./chunk-NWO6GRYE.js";
|
|
11
|
+
import "./chunk-KVRSAC4Z.js";
|
|
10
12
|
import "./chunk-MF3UW36X.js";
|
|
11
13
|
import "./chunk-WGMJCFCE.js";
|
|
12
14
|
import "./chunk-EU3WWTFH.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-7772ENZN.js";
|
|
16
|
+
import "./chunk-DH3SSBZN.js";
|
|
17
|
+
import "./chunk-LXFLRM7M.js";
|
|
18
|
+
import "./chunk-OBQPXUXI.js";
|
|
19
|
+
import "./chunk-4QHDQP2N.js";
|
|
20
|
+
import "./chunk-YMFCFMMB.js";
|
|
17
21
|
import "./chunk-I72ACQ6R.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-UN26GIQG.js";
|
|
22
|
+
import "./chunk-46RORSKU.js";
|
|
20
23
|
import {
|
|
21
24
|
getEventStore
|
|
22
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-NGJ5XJ7D.js";
|
|
26
|
+
import "./chunk-7QXRQ5ZL.js";
|
|
23
27
|
import "./chunk-DJASQ2ZB.js";
|
|
24
28
|
import {
|
|
25
29
|
createChatMessageMessage,
|
|
26
30
|
createPhaseChangedMessage,
|
|
27
31
|
createSessionRunningMessage
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import "./chunk-S7O77CVO.js";
|
|
30
|
-
import "./chunk-QNKXZH3Z.js";
|
|
31
|
-
import "./chunk-I4RWK2L2.js";
|
|
32
|
+
} from "./chunk-O3NUNU4H.js";
|
|
32
33
|
import "./chunk-J2GP3J3X.js";
|
|
33
34
|
import "./chunk-V4IE7HJY.js";
|
|
34
35
|
import "./chunk-YHEQTVFV.js";
|
|
35
36
|
import "./chunk-EZK7CGJ7.js";
|
|
36
37
|
import "./chunk-K44MW7JJ.js";
|
|
37
|
-
import "./chunk-
|
|
38
|
+
import "./chunk-5GOOSZX7.js";
|
|
38
39
|
import "./chunk-WGAAQASA.js";
|
|
39
40
|
import "./chunk-CQGTEGKL.js";
|
|
40
41
|
import "./chunk-5WRI5ZAA.js";
|
|
@@ -158,4 +159,4 @@ export {
|
|
|
158
159
|
startChatSession,
|
|
159
160
|
stopSessionExecution
|
|
160
161
|
};
|
|
161
|
-
//# sourceMappingURL=chat-handler-
|
|
162
|
+
//# sourceMappingURL=chat-handler-3BZ4PDL2.js.map
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getEventStore
|
|
3
|
+
} from "./chunk-NGJ5XJ7D.js";
|
|
4
|
+
import {
|
|
5
|
+
createChatPathConfirmationMessage
|
|
6
|
+
} from "./chunk-O3NUNU4H.js";
|
|
7
|
+
|
|
8
|
+
// src/server/tools/path-security.ts
|
|
9
|
+
import { realpath } from "fs/promises";
|
|
10
|
+
import { resolve, normalize, join, basename, sep, posix, win32 } from "path";
|
|
11
|
+
import { homedir, tmpdir } from "os";
|
|
12
|
+
var SAFE_PATHS = /* @__PURE__ */ new Set([
|
|
13
|
+
"/dev/null",
|
|
14
|
+
"/dev/zero",
|
|
15
|
+
"/dev/random",
|
|
16
|
+
"/dev/urandom",
|
|
17
|
+
"/dev/stdin",
|
|
18
|
+
"/dev/stdout",
|
|
19
|
+
"/dev/stderr",
|
|
20
|
+
"/dev/fd/0",
|
|
21
|
+
"/dev/fd/1",
|
|
22
|
+
"/dev/fd/2"
|
|
23
|
+
]);
|
|
24
|
+
var ALLOWED_ROOTS = ["/tmp", "/var/tmp", tmpdir()];
|
|
25
|
+
var SENSITIVE_FILE_PATTERNS = [
|
|
26
|
+
// Dotenv files (but not .envrc, .env-example, .env.example)
|
|
27
|
+
/^\.env$/,
|
|
28
|
+
// .env
|
|
29
|
+
/^\.env\.(?!example)[a-zA-Z0-9_.-]+$/,
|
|
30
|
+
// .env.local, .env.production (not .env.example)
|
|
31
|
+
// Credential files
|
|
32
|
+
/^credentials\.json$/i,
|
|
33
|
+
/^secrets?\.(?:json|ya?ml|toml)$/i,
|
|
34
|
+
// Private keys
|
|
35
|
+
/\.pem$/i,
|
|
36
|
+
/\.key$/i,
|
|
37
|
+
/^id_rsa/,
|
|
38
|
+
/^id_ed25519/,
|
|
39
|
+
/^id_ecdsa/,
|
|
40
|
+
/^id_dsa/,
|
|
41
|
+
// Auth configs
|
|
42
|
+
/^\.netrc$/
|
|
43
|
+
];
|
|
44
|
+
function isSensitivePath(path) {
|
|
45
|
+
const fileName = basename(path);
|
|
46
|
+
if (!fileName) return false;
|
|
47
|
+
return SENSITIVE_FILE_PATTERNS.some((pattern) => pattern.test(fileName));
|
|
48
|
+
}
|
|
49
|
+
var sessionAllowedPaths = /* @__PURE__ */ new Map();
|
|
50
|
+
function addAllowedPath(sessionId, path) {
|
|
51
|
+
if (!sessionAllowedPaths.has(sessionId)) {
|
|
52
|
+
sessionAllowedPaths.set(sessionId, /* @__PURE__ */ new Set());
|
|
53
|
+
}
|
|
54
|
+
sessionAllowedPaths.get(sessionId).add(normalize(path));
|
|
55
|
+
}
|
|
56
|
+
function addAllowedPaths(sessionId, paths) {
|
|
57
|
+
for (const path of paths) {
|
|
58
|
+
addAllowedPath(sessionId, path);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isPathAllowed(sessionId, path) {
|
|
62
|
+
const allowed = sessionAllowedPaths.get(sessionId);
|
|
63
|
+
if (!allowed) return false;
|
|
64
|
+
return allowed.has(normalize(path));
|
|
65
|
+
}
|
|
66
|
+
function clearAllowedPaths(sessionId) {
|
|
67
|
+
sessionAllowedPaths.delete(sessionId);
|
|
68
|
+
}
|
|
69
|
+
async function safeRealpath(path) {
|
|
70
|
+
const absolutePath = normalize(resolve(path));
|
|
71
|
+
try {
|
|
72
|
+
return await realpath(absolutePath);
|
|
73
|
+
} catch {
|
|
74
|
+
const missingSegments = [];
|
|
75
|
+
let current = absolutePath;
|
|
76
|
+
while (true) {
|
|
77
|
+
try {
|
|
78
|
+
const canonicalParent = await realpath(current);
|
|
79
|
+
return normalize(join(canonicalParent, ...missingSegments.reverse()));
|
|
80
|
+
} catch {
|
|
81
|
+
const parent = resolve(current, "..");
|
|
82
|
+
if (parent === current) return absolutePath;
|
|
83
|
+
missingSegments.push(basename(current));
|
|
84
|
+
current = parent;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function isPathWithinSandbox(path, workdir, sessionId) {
|
|
90
|
+
const normalizedWorkdir = normalize((await safeRealpath(workdir)).replace(/\/+$/, ""));
|
|
91
|
+
let resolvedPath;
|
|
92
|
+
try {
|
|
93
|
+
resolvedPath = normalize(await realpath(path));
|
|
94
|
+
} catch {
|
|
95
|
+
try {
|
|
96
|
+
const { readlink } = await import("fs/promises");
|
|
97
|
+
const linkTarget = await readlink(path);
|
|
98
|
+
const linkDir = resolve(path, "..");
|
|
99
|
+
resolvedPath = await safeRealpath(resolve(linkDir, linkTarget));
|
|
100
|
+
} catch {
|
|
101
|
+
resolvedPath = await safeRealpath(path);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
resolvedPath = resolvedPath.replace(/\/+$/, "");
|
|
105
|
+
if (resolvedPath === normalizedWorkdir || resolvedPath.startsWith(normalizedWorkdir + sep)) {
|
|
106
|
+
return { allowed: true, resolvedPath };
|
|
107
|
+
}
|
|
108
|
+
for (const root of ALLOWED_ROOTS) {
|
|
109
|
+
const normalizedRoot = normalize((await safeRealpath(root)).replace(/\/+$/, ""));
|
|
110
|
+
if (resolvedPath === normalizedRoot || resolvedPath.startsWith(normalizedRoot + sep)) {
|
|
111
|
+
return { allowed: true, resolvedPath };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (sessionId && (isPathAllowed(sessionId, path) || isPathAllowed(sessionId, resolvedPath))) {
|
|
115
|
+
return { allowed: true, resolvedPath };
|
|
116
|
+
}
|
|
117
|
+
return { allowed: false, resolvedPath };
|
|
118
|
+
}
|
|
119
|
+
function looksLikeRegex(str) {
|
|
120
|
+
return /[*?+[\]\\]/.test(str);
|
|
121
|
+
}
|
|
122
|
+
var isWindows = () => process.platform === "win32";
|
|
123
|
+
function isWindowsAbsolutePath(str) {
|
|
124
|
+
return /^[A-Za-z]:[\\/]/.test(str);
|
|
125
|
+
}
|
|
126
|
+
function normalizeExtracted(path) {
|
|
127
|
+
return isWindowsAbsolutePath(path) ? win32.normalize(path) : posix.normalize(path);
|
|
128
|
+
}
|
|
129
|
+
function isPlaceholderToken(str) {
|
|
130
|
+
return str.includes("__URL__") || str.includes("__FILEURL__") || str.includes("__SED__") || str.includes("__COMMIT_MSG__");
|
|
131
|
+
}
|
|
132
|
+
function extractAbsolutePathsFromCommand(command) {
|
|
133
|
+
if (!command.trim()) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const paths = [];
|
|
137
|
+
const home = homedir();
|
|
138
|
+
let sanitized = command.replace(/https?:\/\/[^\s'"]+/g, " __URL__ ").replace(/ftp:\/\/[^\s'"]+/g, " __URL__ ");
|
|
139
|
+
sanitized = sanitized.replace(/(?<!\w)s\/[^/]*\/[^/]*\/[gip]*/g, " __SED__ ");
|
|
140
|
+
sanitized = sanitized.replace(/(?<!\w)s\|[^|]*\|[^|]*\|[gip]*/g, " __SED__ ");
|
|
141
|
+
sanitized = sanitized.replace(/(?<!\w)s:[^:]*:[^:]*:[gip]*/g, " __SED__ ");
|
|
142
|
+
sanitized = sanitized.replace(
|
|
143
|
+
/git\s+commit\b.*?(?:-(?:[a-zA-Z]*m)(?=\s)|--message)\s+(["'])(?:(?!\1).)*\1/g,
|
|
144
|
+
(match2) => match2.replace(/\/[^\s"'|&;<>`()]+/g, " __COMMIT_MSG__ ")
|
|
145
|
+
);
|
|
146
|
+
const fileUrlMatches = command.matchAll(/file:\/\/([^\s'"]+)/g);
|
|
147
|
+
for (const match2 of fileUrlMatches) {
|
|
148
|
+
const filePath = match2[1];
|
|
149
|
+
if (filePath && !isSafePath(filePath)) {
|
|
150
|
+
paths.push(normalizeExtracted(filePath));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
sanitized = sanitized.replace(/file:\/\/[^\s'"]+/g, " __FILEURL__ ");
|
|
154
|
+
const tildePattern = /(?:^|[\s='"(])~(\/[^\s'"()]*)?(?=[\s'"()]|$)/g;
|
|
155
|
+
let match;
|
|
156
|
+
while ((match = tildePattern.exec(sanitized)) !== null) {
|
|
157
|
+
const pathPart = match[1] ?? "";
|
|
158
|
+
const fullPath = home + pathPart;
|
|
159
|
+
const resolved = normalize(resolve(fullPath));
|
|
160
|
+
if (!isSafePath(resolved)) {
|
|
161
|
+
paths.push(resolved);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const quotedPattern = /["']([^"']+)["']/g;
|
|
165
|
+
while ((match = quotedPattern.exec(sanitized)) !== null) {
|
|
166
|
+
const content = match[1];
|
|
167
|
+
if (content.startsWith("/") && content.endsWith("/")) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (content.startsWith("/") && looksLikeRegex(content)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (isWindowsAbsolutePath(content)) {
|
|
174
|
+
const resolved = normalizeExtracted(content);
|
|
175
|
+
if (!isSafePath(resolved)) {
|
|
176
|
+
paths.push(resolved);
|
|
177
|
+
}
|
|
178
|
+
} else if (!isWindows() && content.startsWith("/")) {
|
|
179
|
+
const resolved = normalizeExtracted(content);
|
|
180
|
+
if (!isSafePath(resolved)) {
|
|
181
|
+
paths.push(resolved);
|
|
182
|
+
}
|
|
183
|
+
} else if (content.startsWith("~")) {
|
|
184
|
+
const pathPart = content.slice(1);
|
|
185
|
+
const fullPath = join(home, pathPart);
|
|
186
|
+
const resolved = normalize(resolve(fullPath));
|
|
187
|
+
if (!isSafePath(resolved)) {
|
|
188
|
+
paths.push(resolved);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (isWindows()) {
|
|
193
|
+
const winAbsolutePattern = /(?:^|[\s=(])([A-Za-z]:[\\/][^\s"'|&;<>`()]+)/g;
|
|
194
|
+
while ((match = winAbsolutePattern.exec(sanitized)) !== null) {
|
|
195
|
+
const candidate = match[1];
|
|
196
|
+
if (isPlaceholderToken(candidate)) continue;
|
|
197
|
+
const resolved = normalizeExtracted(candidate);
|
|
198
|
+
if (!isSafePath(resolved)) {
|
|
199
|
+
paths.push(resolved);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
const absolutePattern = /(?:^|[\s=(])(\/[^\s"'|&;<>`()]+)/g;
|
|
204
|
+
while ((match = absolutePattern.exec(sanitized)) !== null) {
|
|
205
|
+
const candidate = match[1];
|
|
206
|
+
if (isPlaceholderToken(candidate)) continue;
|
|
207
|
+
if (looksLikeRegex(candidate)) continue;
|
|
208
|
+
const resolved = normalizeExtracted(candidate);
|
|
209
|
+
if (resolved === "/" || resolved === "") continue;
|
|
210
|
+
if (!isSafePath(resolved)) {
|
|
211
|
+
paths.push(resolved);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return [...new Set(paths)];
|
|
216
|
+
}
|
|
217
|
+
function extractSensitivePathsFromCommand(command) {
|
|
218
|
+
if (!command.trim()) {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
const paths = [];
|
|
222
|
+
const quotedPattern = /["']([^"']+)["']/g;
|
|
223
|
+
let match;
|
|
224
|
+
while ((match = quotedPattern.exec(command)) !== null) {
|
|
225
|
+
const content = match[1];
|
|
226
|
+
if (isSensitivePath(content)) {
|
|
227
|
+
paths.push(content);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const unquoted = command.replace(/["'][^"']*["']/g, " ").replace(/[|&;><]/g, " ");
|
|
231
|
+
const tokens = unquoted.split(/\s+/).filter(Boolean);
|
|
232
|
+
for (const token of tokens) {
|
|
233
|
+
if (token.startsWith("-")) continue;
|
|
234
|
+
if (token.includes("://")) continue;
|
|
235
|
+
if (isSensitivePath(token)) {
|
|
236
|
+
paths.push(token);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return [...new Set(paths)];
|
|
240
|
+
}
|
|
241
|
+
function isSafePath(path) {
|
|
242
|
+
const normalized = posix.normalize(path);
|
|
243
|
+
return SAFE_PATHS.has(normalized);
|
|
244
|
+
}
|
|
245
|
+
async function checkPathsAccess(paths, workdir, sessionId) {
|
|
246
|
+
if (paths.length === 0) {
|
|
247
|
+
return { needsConfirmation: false, deniedPaths: [], sensitivePaths: [] };
|
|
248
|
+
}
|
|
249
|
+
const deniedPaths = [];
|
|
250
|
+
const sensitivePaths = [];
|
|
251
|
+
for (const path of paths) {
|
|
252
|
+
const result = await isPathWithinSandbox(path, workdir, sessionId);
|
|
253
|
+
if (!result.allowed) {
|
|
254
|
+
deniedPaths.push(result.resolvedPath);
|
|
255
|
+
}
|
|
256
|
+
if (isSensitivePath(path) && !isPathAllowed(sessionId ?? "", path)) {
|
|
257
|
+
sensitivePaths.push(result.resolvedPath);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const uniqueDenied = [...new Set(deniedPaths)];
|
|
261
|
+
const uniqueSensitive = [...new Set(sensitivePaths)];
|
|
262
|
+
return {
|
|
263
|
+
needsConfirmation: uniqueDenied.length > 0 || uniqueSensitive.length > 0,
|
|
264
|
+
deniedPaths: uniqueDenied,
|
|
265
|
+
sensitivePaths: uniqueSensitive
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
var DANGEROUS_PATTERNS = [
|
|
269
|
+
/sudo\s/,
|
|
270
|
+
/rm\s+(-rf?|--recursive)\s+[~]/,
|
|
271
|
+
/chmod\s+777/,
|
|
272
|
+
/>\s*\/dev\/sd/,
|
|
273
|
+
/mkfs\s/,
|
|
274
|
+
/dd\s+if=/,
|
|
275
|
+
/:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/
|
|
276
|
+
];
|
|
277
|
+
function extractDangerousPatterns(command) {
|
|
278
|
+
const dangerous = [];
|
|
279
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
280
|
+
if (pattern.test(command)) {
|
|
281
|
+
dangerous.push(pattern.source);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return dangerous;
|
|
285
|
+
}
|
|
286
|
+
function extractGitNoVerify(command) {
|
|
287
|
+
const subCommands = command.split(/\s*(?:&&|\|\||\||;)\s*/);
|
|
288
|
+
for (const sub of subCommands) {
|
|
289
|
+
const parts = sub.trim().split(/\s+/);
|
|
290
|
+
const gitIndex = parts.indexOf("git");
|
|
291
|
+
const subCmd = parts[gitIndex + 1];
|
|
292
|
+
if (gitIndex >= 0 && subCmd && !subCmd.startsWith("-")) {
|
|
293
|
+
const gitArgs = parts.slice(gitIndex + 2);
|
|
294
|
+
if (gitArgs.some((a) => a === "--no-verify" || a === "-n")) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
async function requestPathAccess(paths, workdir, sessionId, callId, tool, onEvent, dangerLevel, command, isSubAgent) {
|
|
302
|
+
if (isSubAgent) {
|
|
303
|
+
const result2 = await checkPathsAccess(paths, workdir, sessionId);
|
|
304
|
+
if (!result2.needsConfirmation) return;
|
|
305
|
+
if (dangerLevel === "dangerous") {
|
|
306
|
+
const allPaths2 = [.../* @__PURE__ */ new Set([...result2.deniedPaths, ...result2.sensitivePaths])];
|
|
307
|
+
addAllowedPaths(sessionId, allPaths2);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const allPaths = [.../* @__PURE__ */ new Set([...result2.deniedPaths, ...result2.sensitivePaths])];
|
|
311
|
+
const hasDenied2 = result2.deniedPaths.length > 0;
|
|
312
|
+
const hasSensitive2 = result2.sensitivePaths.length > 0;
|
|
313
|
+
const reason2 = hasDenied2 && hasSensitive2 ? "both" : hasDenied2 ? "outside_workdir" : "sensitive_file";
|
|
314
|
+
throw new PathAccessDeniedError(allPaths, tool, reason2);
|
|
315
|
+
}
|
|
316
|
+
const emitPendingEvent = (confirmationPaths, confirmationReason) => {
|
|
317
|
+
try {
|
|
318
|
+
const eventStore = getEventStore();
|
|
319
|
+
eventStore.append(sessionId, {
|
|
320
|
+
type: "path.confirmation_pending",
|
|
321
|
+
data: { callId, tool, paths: confirmationPaths, workdir, reason: confirmationReason }
|
|
322
|
+
});
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
if (command && extractGitNoVerify(command)) {
|
|
327
|
+
emitPendingEvent([workdir], "git_no_verify");
|
|
328
|
+
const confirmationPromise2 = registerPathConfirmation(callId, [workdir], sessionId, tool, workdir, "git_no_verify");
|
|
329
|
+
onEvent(createChatPathConfirmationMessage(callId, tool, ["git --no-verify detected"], workdir, "git_no_verify"));
|
|
330
|
+
const approved2 = await confirmationPromise2;
|
|
331
|
+
if (!approved2) {
|
|
332
|
+
throw new PathAccessDeniedError(
|
|
333
|
+
["git --no-verify"],
|
|
334
|
+
tool,
|
|
335
|
+
"git_no_verify",
|
|
336
|
+
"User denied git command with --no-verify. The agent must not use --no-verify and must resolve the issue (e.g., fix lint errors, resolve conflicts) that prevents the commit."
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (dangerLevel !== "dangerous" && command) {
|
|
341
|
+
const dangerousPatterns = extractDangerousPatterns(command);
|
|
342
|
+
if (dangerousPatterns.length > 0) {
|
|
343
|
+
emitPendingEvent([workdir], "dangerous_command");
|
|
344
|
+
const confirmationPromise2 = registerPathConfirmation(
|
|
345
|
+
callId,
|
|
346
|
+
[workdir],
|
|
347
|
+
sessionId,
|
|
348
|
+
tool,
|
|
349
|
+
workdir,
|
|
350
|
+
"dangerous_command"
|
|
351
|
+
);
|
|
352
|
+
onEvent(
|
|
353
|
+
createChatPathConfirmationMessage(callId, tool, [dangerousPatterns.join(", ")], workdir, "dangerous_command")
|
|
354
|
+
);
|
|
355
|
+
const approved2 = await confirmationPromise2;
|
|
356
|
+
if (!approved2) {
|
|
357
|
+
throw new PathAccessDeniedError(dangerousPatterns, tool, "dangerous_command");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const result = await checkPathsAccess(paths, workdir, sessionId);
|
|
362
|
+
if (!result.needsConfirmation) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (dangerLevel === "dangerous") {
|
|
366
|
+
const allPaths = [.../* @__PURE__ */ new Set([...result.deniedPaths, ...result.sensitivePaths])];
|
|
367
|
+
addAllowedPaths(sessionId, allPaths);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const allPathsNeedingConfirmation = [.../* @__PURE__ */ new Set([...result.deniedPaths, ...result.sensitivePaths])];
|
|
371
|
+
const hasDenied = result.deniedPaths.length > 0;
|
|
372
|
+
const hasSensitive = result.sensitivePaths.length > 0;
|
|
373
|
+
const reason = hasDenied && hasSensitive ? "both" : hasDenied ? "outside_workdir" : "sensitive_file";
|
|
374
|
+
emitPendingEvent(allPathsNeedingConfirmation, reason);
|
|
375
|
+
const confirmationPromise = registerPathConfirmation(
|
|
376
|
+
callId,
|
|
377
|
+
allPathsNeedingConfirmation,
|
|
378
|
+
sessionId,
|
|
379
|
+
tool,
|
|
380
|
+
workdir,
|
|
381
|
+
reason
|
|
382
|
+
);
|
|
383
|
+
onEvent(createChatPathConfirmationMessage(callId, tool, allPathsNeedingConfirmation, workdir, reason));
|
|
384
|
+
const approved = await confirmationPromise;
|
|
385
|
+
if (!approved) {
|
|
386
|
+
throw new PathAccessDeniedError(allPathsNeedingConfirmation, tool, reason);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
var PathAccessDeniedError = class extends Error {
|
|
390
|
+
constructor(paths, tool, reason = "outside_workdir", customMessage) {
|
|
391
|
+
const reasonText = reason === "sensitive_file" ? "sensitive files (may contain secrets)" : reason === "both" ? "paths outside workdir and sensitive files" : reason === "git_no_verify" ? "git commands with --no-verify" : "paths outside workdir";
|
|
392
|
+
super(customMessage ?? `User denied access to ${reasonText}: ${paths.join(", ")}`);
|
|
393
|
+
this.paths = paths;
|
|
394
|
+
this.tool = tool;
|
|
395
|
+
this.reason = reason;
|
|
396
|
+
this.customMessage = customMessage;
|
|
397
|
+
this.name = "PathAccessDeniedError";
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var pendingConfirmations = /* @__PURE__ */ new Map();
|
|
401
|
+
function registerPathConfirmation(callId, paths, sessionId, tool, workdir, reason) {
|
|
402
|
+
return new Promise((resolve2, reject) => {
|
|
403
|
+
pendingConfirmations.set(callId, { resolve: resolve2, reject, paths, sessionId, tool, workdir, reason });
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function providePathConfirmation(callId, approved, alwaysAllow) {
|
|
407
|
+
const pending = pendingConfirmations.get(callId);
|
|
408
|
+
if (!pending) {
|
|
409
|
+
return { found: false };
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const eventStore = getEventStore();
|
|
413
|
+
eventStore.append(pending.sessionId, {
|
|
414
|
+
type: "path.confirmation_responded",
|
|
415
|
+
data: { callId, approved, alwaysAllow: alwaysAllow ?? false }
|
|
416
|
+
});
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
if (approved) {
|
|
420
|
+
if (alwaysAllow) {
|
|
421
|
+
addAllowedPaths(pending.sessionId, pending.paths);
|
|
422
|
+
} else {
|
|
423
|
+
addAllowedPaths(pending.sessionId, pending.paths);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
pending.resolve(approved);
|
|
427
|
+
pendingConfirmations.delete(callId);
|
|
428
|
+
return { found: true, sessionId: pending.sessionId, approved };
|
|
429
|
+
}
|
|
430
|
+
function cancelPathConfirmation(callId, reason) {
|
|
431
|
+
const pending = pendingConfirmations.get(callId);
|
|
432
|
+
if (!pending) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
pending.reject(new Error(reason));
|
|
436
|
+
pendingConfirmations.delete(callId);
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
function cancelPathConfirmationsForSession(sessionId, reason) {
|
|
440
|
+
let cancelledCount = 0;
|
|
441
|
+
for (const [callId, pending] of pendingConfirmations.entries()) {
|
|
442
|
+
if (pending.sessionId !== sessionId) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
pending.reject(new Error(reason));
|
|
446
|
+
pendingConfirmations.delete(callId);
|
|
447
|
+
cancelledCount += 1;
|
|
448
|
+
}
|
|
449
|
+
return cancelledCount;
|
|
450
|
+
}
|
|
451
|
+
function hasPendingPathConfirmation(callId) {
|
|
452
|
+
return pendingConfirmations.has(callId);
|
|
453
|
+
}
|
|
454
|
+
function getPendingConfirmationsBySession() {
|
|
455
|
+
const bySession = {};
|
|
456
|
+
for (const [_callId, pending] of pendingConfirmations.entries()) {
|
|
457
|
+
const entry = {
|
|
458
|
+
callId: _callId,
|
|
459
|
+
tool: pending.tool,
|
|
460
|
+
paths: pending.paths,
|
|
461
|
+
workdir: pending.workdir,
|
|
462
|
+
reason: pending.reason
|
|
463
|
+
};
|
|
464
|
+
const existing = bySession[pending.sessionId];
|
|
465
|
+
if (existing) {
|
|
466
|
+
existing.push(entry);
|
|
467
|
+
} else {
|
|
468
|
+
bySession[pending.sessionId] = [entry];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return bySession;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export {
|
|
475
|
+
isSensitivePath,
|
|
476
|
+
addAllowedPath,
|
|
477
|
+
addAllowedPaths,
|
|
478
|
+
isPathAllowed,
|
|
479
|
+
clearAllowedPaths,
|
|
480
|
+
isPathWithinSandbox,
|
|
481
|
+
extractAbsolutePathsFromCommand,
|
|
482
|
+
extractSensitivePathsFromCommand,
|
|
483
|
+
checkPathsAccess,
|
|
484
|
+
extractDangerousPatterns,
|
|
485
|
+
extractGitNoVerify,
|
|
486
|
+
requestPathAccess,
|
|
487
|
+
PathAccessDeniedError,
|
|
488
|
+
registerPathConfirmation,
|
|
489
|
+
providePathConfirmation,
|
|
490
|
+
cancelPathConfirmation,
|
|
491
|
+
cancelPathConfirmationsForSession,
|
|
492
|
+
hasPendingPathConfirmation,
|
|
493
|
+
getPendingConfirmationsBySession
|
|
494
|
+
};
|
|
495
|
+
//# sourceMappingURL=chunk-46RORSKU.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SETTINGS_KEYS,
|
|
3
3
|
getSetting
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YMFCFMMB.js";
|
|
5
5
|
|
|
6
6
|
// src/server/utils/platform.ts
|
|
7
7
|
import os from "os";
|
|
@@ -63,4 +63,4 @@ export {
|
|
|
63
63
|
isWindowsPath,
|
|
64
64
|
isAbsolutePath
|
|
65
65
|
};
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
66
|
+
//# sourceMappingURL=chunk-4QHDQP2N.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getPlatformShell
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4QHDQP2N.js";
|
|
4
4
|
import {
|
|
5
5
|
streamWithSegments
|
|
6
6
|
} from "./chunk-J2GP3J3X.js";
|
|
@@ -419,13 +419,14 @@ async function* streamLLMPure(options) {
|
|
|
419
419
|
const fullName = toolNames.get(value.index);
|
|
420
420
|
if (!seenToolIndices.has(value.index) && fullName) {
|
|
421
421
|
seenToolIndices.add(value.index);
|
|
422
|
+
const displayName = options.subAgentAliases?.has(fullName) ? "call_sub_agent" : fullName;
|
|
422
423
|
const accumulatedArgs = toolArgs.get(value.index);
|
|
423
424
|
yield {
|
|
424
425
|
type: "tool.preparing",
|
|
425
426
|
data: {
|
|
426
427
|
messageId,
|
|
427
428
|
index: value.index,
|
|
428
|
-
name:
|
|
429
|
+
name: displayName,
|
|
429
430
|
...accumulatedArgs ? { arguments: accumulatedArgs } : {}
|
|
430
431
|
}
|
|
431
432
|
};
|
|
@@ -678,4 +679,4 @@ export {
|
|
|
678
679
|
createChatDoneEvent,
|
|
679
680
|
consumeStreamGenerator
|
|
680
681
|
};
|
|
681
|
-
//# sourceMappingURL=chunk-
|
|
682
|
+
//# sourceMappingURL=chunk-7772ENZN.js.map
|
|
@@ -176,8 +176,8 @@ function runMigrations(db2) {
|
|
|
176
176
|
if (!columnNames.includes("workspace") && columnNames.includes("worktree")) {
|
|
177
177
|
logger.info("Migrating sessions table: renaming worktree to workspace");
|
|
178
178
|
db2.exec(`ALTER TABLE sessions RENAME COLUMN worktree TO workspace`);
|
|
179
|
-
|
|
180
|
-
if (!columnNames.includes("workspace")) {
|
|
179
|
+
columnNames[columnNames.indexOf("worktree")] = "workspace";
|
|
180
|
+
} else if (!columnNames.includes("workspace")) {
|
|
181
181
|
logger.info("Migrating sessions table: adding workspace column");
|
|
182
182
|
db2.exec(`ALTER TABLE sessions ADD COLUMN workspace TEXT`);
|
|
183
183
|
}
|
|
@@ -188,4 +188,4 @@ export {
|
|
|
188
188
|
initDatabase,
|
|
189
189
|
getDatabase
|
|
190
190
|
};
|
|
191
|
-
//# sourceMappingURL=chunk-
|
|
191
|
+
//# sourceMappingURL=chunk-7QXRQ5ZL.js.map
|