@cjhyy/code-shell-server 0.9.31 → 0.9.32
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.
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { InstalledPanelApp } from "@cjhyy/code-shell-core";
|
|
2
2
|
export type PanelDirectoryAuthorizer = (app: InstalledPanelApp, projectPath: string, workspacePath: string) => Promise<void>;
|
|
3
|
+
/** Host-only proof that a workspace still belongs to the expected main project. */
|
|
4
|
+
export declare function createPanelDirectoryProjectScope(workspacePath: string, expectedProjectPath: string): {
|
|
5
|
+
projectPath: string;
|
|
6
|
+
assertCurrent(): void;
|
|
7
|
+
};
|
|
3
8
|
export declare function desktopPanelDirectoryBookmarks(dataDir: string): PanelAppDirectoryBookmarks;
|
|
4
9
|
/** The file mutex uses a sibling of its parent; keep both inside the data volume. */
|
|
5
10
|
export declare function hubPanelDirectoryBookmarks(dataDir: string): PanelAppDirectoryBookmarks;
|
|
@@ -9,6 +14,8 @@ export declare class PanelAppDirectoryBookmarks {
|
|
|
9
14
|
private readonly options;
|
|
10
15
|
constructor(file: string, options?: {
|
|
11
16
|
legacyFiles?: readonly string[];
|
|
17
|
+
/** Supplied only by a trusted Host, never by Panel or browser input. */
|
|
18
|
+
isLegacyProjectScope?: (expectedProjectPath: string, legacyProjectPath: string) => boolean;
|
|
12
19
|
});
|
|
13
20
|
remember(appId: string, projectPath: string, path: string): string;
|
|
14
21
|
restore(appId: string, projectPath: string, id: unknown): string;
|
|
@@ -2,9 +2,58 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { chmodSync, closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { dirname, isAbsolute, join } from "node:path";
|
|
4
4
|
import { acquireFileLock } from "@cjhyy/code-shell-core/internal";
|
|
5
|
+
import { createHubPanelBinding } from "./hub-binding.js";
|
|
6
|
+
/** Host-only proof that a workspace still belongs to the expected main project. */
|
|
7
|
+
export function createPanelDirectoryProjectScope(workspacePath, expectedProjectPath) {
|
|
8
|
+
for (const path of [workspacePath, expectedProjectPath]) {
|
|
9
|
+
if (!isAbsolute(path) || path.length > 32_768 || path.includes("\0"))
|
|
10
|
+
throw new Error("Invalid Panel directory project scope");
|
|
11
|
+
}
|
|
12
|
+
const binding = createHubPanelBinding(workspacePath);
|
|
13
|
+
binding.assertBinding();
|
|
14
|
+
const projectPath = realpathSync(expectedProjectPath);
|
|
15
|
+
const identity = lstatSync(projectPath);
|
|
16
|
+
if (!identity.isDirectory() ||
|
|
17
|
+
identity.isSymbolicLink() ||
|
|
18
|
+
realpathSync(binding.bindingCwd) !== projectPath)
|
|
19
|
+
throw new Error("Panel directory project scope is unavailable");
|
|
20
|
+
const assertCurrent = () => {
|
|
21
|
+
binding.assertBinding();
|
|
22
|
+
const current = lstatSync(projectPath);
|
|
23
|
+
if (!current.isDirectory() ||
|
|
24
|
+
current.isSymbolicLink() ||
|
|
25
|
+
current.dev !== identity.dev ||
|
|
26
|
+
current.ino !== identity.ino ||
|
|
27
|
+
realpathSync(expectedProjectPath) !== projectPath ||
|
|
28
|
+
realpathSync(binding.bindingCwd) !== projectPath)
|
|
29
|
+
throw new Error("Panel directory project scope changed");
|
|
30
|
+
};
|
|
31
|
+
assertCurrent();
|
|
32
|
+
return { projectPath, assertCurrent };
|
|
33
|
+
}
|
|
5
34
|
export function desktopPanelDirectoryBookmarks(dataDir) {
|
|
35
|
+
const scopes = new Map();
|
|
6
36
|
return new PanelAppDirectoryBookmarks(join(dataDir, "panel-app-directory-bookmarks.json"), {
|
|
7
37
|
legacyFiles: [join(dataDir, "panel-web-directory-bookmarks.json")],
|
|
38
|
+
isLegacyProjectScope(expectedProjectPath, legacyProjectPath) {
|
|
39
|
+
const key = JSON.stringify([expectedProjectPath, legacyProjectPath]);
|
|
40
|
+
try {
|
|
41
|
+
let scope = scopes.get(key);
|
|
42
|
+
if (!scope) {
|
|
43
|
+
scope = createPanelDirectoryProjectScope(legacyProjectPath, expectedProjectPath);
|
|
44
|
+
if (scopes.size >= 64)
|
|
45
|
+
scopes.delete(scopes.keys().next().value);
|
|
46
|
+
scopes.set(key, scope);
|
|
47
|
+
}
|
|
48
|
+
// Keep a failed proof frozen: replacing Git metadata must not silently
|
|
49
|
+
// reauthorize this instance's previously verified worktree association.
|
|
50
|
+
scope.assertCurrent();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
8
57
|
});
|
|
9
58
|
}
|
|
10
59
|
/** The file mutex uses a sibling of its parent; keep both inside the data volume. */
|
|
@@ -156,8 +205,22 @@ export class PanelAppDirectoryBookmarks {
|
|
|
156
205
|
}
|
|
157
206
|
}
|
|
158
207
|
}
|
|
159
|
-
if (!bookmark || bookmark.appId !== appId
|
|
208
|
+
if (!bookmark || bookmark.appId !== appId)
|
|
160
209
|
throw new Error("Saved directory is unavailable; choose it again");
|
|
210
|
+
const savedProjectPath = bookmark.projectPath;
|
|
211
|
+
const assertProjectScope = () => {
|
|
212
|
+
if (savedProjectPath === projectPath)
|
|
213
|
+
return;
|
|
214
|
+
try {
|
|
215
|
+
if (this.options.isLegacyProjectScope?.(projectPath, savedProjectPath) === true)
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// The Host proof is optional; a failed proof never relaxes exact scope.
|
|
220
|
+
}
|
|
221
|
+
throw new Error("Saved directory is unavailable; choose it again");
|
|
222
|
+
};
|
|
223
|
+
assertProjectScope();
|
|
161
224
|
try {
|
|
162
225
|
const info = lstatSync(bookmark.path);
|
|
163
226
|
if (!info.isDirectory() ||
|
|
@@ -195,6 +258,7 @@ export class PanelAppDirectoryBookmarks {
|
|
|
195
258
|
release();
|
|
196
259
|
}
|
|
197
260
|
}
|
|
261
|
+
assertProjectScope();
|
|
198
262
|
return bookmark.path;
|
|
199
263
|
}
|
|
200
264
|
}
|
|
@@ -84,7 +84,9 @@ export declare class PanelAppProcessService {
|
|
|
84
84
|
name: string;
|
|
85
85
|
handle?: string;
|
|
86
86
|
}>;
|
|
87
|
-
grantDirectory(owner: PanelProcessOwner, path: string
|
|
87
|
+
grantDirectory(owner: PanelProcessOwner, path: string, options?: {
|
|
88
|
+
assertCurrent?: () => void;
|
|
89
|
+
}): Promise<{
|
|
88
90
|
handle: string;
|
|
89
91
|
path: string;
|
|
90
92
|
name: string;
|
|
@@ -221,14 +221,20 @@ export class PanelAppProcessService {
|
|
|
221
221
|
});
|
|
222
222
|
return { available: true, name, handle };
|
|
223
223
|
}
|
|
224
|
-
async grantDirectory(owner, path) {
|
|
224
|
+
async grantDirectory(owner, path, options = {}) {
|
|
225
225
|
owner = { ...owner };
|
|
226
226
|
const epoch = this.epoch(owner);
|
|
227
|
+
options.assertCurrent?.();
|
|
227
228
|
await this.authorize(owner, epoch);
|
|
229
|
+
options.assertCurrent?.();
|
|
228
230
|
const resolved = await realpath(path);
|
|
231
|
+
options.assertCurrent?.();
|
|
229
232
|
await this.authorize(owner, epoch);
|
|
233
|
+
options.assertCurrent?.();
|
|
230
234
|
const info = await stat(resolved);
|
|
235
|
+
options.assertCurrent?.();
|
|
231
236
|
await this.authorize(owner, epoch);
|
|
237
|
+
options.assertCurrent?.();
|
|
232
238
|
this.assertLive(owner, epoch);
|
|
233
239
|
if (!info.isDirectory())
|
|
234
240
|
throw new Error("selected process directory is not a directory");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjhyy/code-shell-server",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.32",
|
|
4
4
|
"description": "CodeShell server transport layer — mobile remote HTTP/WS host, pairing, passcode gate, tunnel, rooms, uploads, and session/attachment services. Electron-free; precursor of the auth gateway.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@cjhyy/code-shell-capability-coding": "0.9.
|
|
64
|
-
"@cjhyy/code-shell-core": "0.9.
|
|
65
|
-
"@cjhyy/code-shell-web": "0.9.
|
|
63
|
+
"@cjhyy/code-shell-capability-coding": "0.9.32",
|
|
64
|
+
"@cjhyy/code-shell-core": "0.9.32",
|
|
65
|
+
"@cjhyy/code-shell-web": "0.9.32",
|
|
66
66
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
67
67
|
"@types/ws": "^8.18.1",
|
|
68
68
|
"ws": "^8.21.0",
|
|
69
|
-
"@cjhyy/code-shell-link": "0.9.
|
|
69
|
+
"@cjhyy/code-shell-link": "0.9.32"
|
|
70
70
|
},
|
|
71
71
|
"engines": {
|
|
72
72
|
"node": ">=20.10"
|