jishushell 0.4.30 → 0.5.15
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/apps/anythingllm-container.yaml +287 -0
- package/apps/browserless-chromium-container.yaml +18 -6
- package/apps/filebrowser-container.yaml +163 -0
- package/apps/openclaw-binary.yaml +8 -0
- package/apps/openclaw-container.yaml +9 -1
- package/apps/openclaw-with-searxng-container.yaml +4 -0
- package/apps/searxng-container.yaml +5 -4
- package/apps/weknora-container.yaml +471 -0
- package/dist/cli/panel.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +99 -1
- package/dist/config.js.map +1 -1
- package/dist/install.js +3 -3
- package/dist/install.js.map +1 -1
- package/dist/routes/auth.js +2 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/backup.js +64 -11
- package/dist/routes/backup.js.map +1 -1
- package/dist/routes/external-mounts.d.ts +17 -0
- package/dist/routes/external-mounts.js +73 -0
- package/dist/routes/external-mounts.js.map +1 -0
- package/dist/routes/file-mounts.d.ts +13 -0
- package/dist/routes/file-mounts.js +90 -0
- package/dist/routes/file-mounts.js.map +1 -0
- package/dist/routes/files-organize.d.ts +28 -0
- package/dist/routes/files-organize.js +167 -0
- package/dist/routes/files-organize.js.map +1 -0
- package/dist/routes/files.d.ts +31 -0
- package/dist/routes/files.js +321 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/instances.js +45 -7
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/internal.d.ts +2 -0
- package/dist/routes/internal.js +59 -0
- package/dist/routes/internal.js.map +1 -0
- package/dist/routes/setup.js +9 -9
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +1 -1
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/webdav.d.ts +17 -0
- package/dist/routes/webdav.js +114 -0
- package/dist/routes/webdav.js.map +1 -0
- package/dist/server.js +341 -3
- package/dist/server.js.map +1 -1
- package/dist/services/app/app-compiler.d.ts +1 -1
- package/dist/services/app/app-compiler.js +5 -5
- package/dist/services/app/app-compiler.js.map +1 -1
- package/dist/services/app/app-manager.d.ts +1 -0
- package/dist/services/app/app-manager.js +172 -41
- package/dist/services/app/app-manager.js.map +1 -1
- package/dist/services/app/custom-manager.js.map +1 -1
- package/dist/services/app/hermes-agent-manager.js +1 -0
- package/dist/services/app/hermes-agent-manager.js.map +1 -1
- package/dist/services/app/ollama-manager.js +1 -1
- package/dist/services/app/ollama-manager.js.map +1 -1
- package/dist/services/app/openclaw-manager.js +20 -3
- package/dist/services/app/openclaw-manager.js.map +1 -1
- package/dist/services/app/platform-transform.d.ts +32 -0
- package/dist/services/app/platform-transform.js +65 -0
- package/dist/services/app/platform-transform.js.map +1 -0
- package/dist/services/app-passwords.d.ts +61 -0
- package/dist/services/app-passwords.js +173 -0
- package/dist/services/app-passwords.js.map +1 -0
- package/dist/services/backup-manager.d.ts +11 -0
- package/dist/services/backup-manager.js +177 -4
- package/dist/services/backup-manager.js.map +1 -1
- package/dist/services/connection-apply.d.ts +2 -0
- package/dist/services/connection-apply.js +55 -1
- package/dist/services/connection-apply.js.map +1 -1
- package/dist/services/connection-resolver.js +1 -1
- package/dist/services/connection-resolver.js.map +1 -1
- package/dist/services/connection-transactor.d.ts +2 -0
- package/dist/services/connection-transactor.js +12 -2
- package/dist/services/connection-transactor.js.map +1 -1
- package/dist/services/external-mounts.d.ts +40 -0
- package/dist/services/external-mounts.js +187 -0
- package/dist/services/external-mounts.js.map +1 -0
- package/dist/services/files-manager.d.ts +252 -0
- package/dist/services/files-manager.js +1075 -0
- package/dist/services/files-manager.js.map +1 -0
- package/dist/services/files-mounts.d.ts +42 -0
- package/dist/services/files-mounts.js +207 -0
- package/dist/services/files-mounts.js.map +1 -0
- package/dist/services/instance-manager.js +1 -23
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/ssrf.js +6 -2
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/nomad-manager.d.ts +4 -0
- package/dist/services/nomad-manager.js +53 -19
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/organize/applier.d.ts +46 -0
- package/dist/services/organize/applier.js +218 -0
- package/dist/services/organize/applier.js.map +1 -0
- package/dist/services/organize/rules.d.ts +57 -0
- package/dist/services/organize/rules.js +286 -0
- package/dist/services/organize/rules.js.map +1 -0
- package/dist/services/organize/scanner.d.ts +50 -0
- package/dist/services/organize/scanner.js +366 -0
- package/dist/services/organize/scanner.js.map +1 -0
- package/dist/services/organize/store.d.ts +14 -0
- package/dist/services/organize/store.js +82 -0
- package/dist/services/organize/store.js.map +1 -0
- package/dist/services/panel-manager.js +20 -1
- package/dist/services/panel-manager.js.map +1 -1
- package/dist/services/process-manager.js +3 -2
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/runtime/adapters/hermes.js +1 -1
- package/dist/services/runtime/adapters/hermes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
- package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
- package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw.d.ts +90 -0
- package/dist/services/runtime/adapters/openclaw.js +957 -45
- package/dist/services/runtime/adapters/openclaw.js.map +1 -1
- package/dist/services/runtime/instance.d.ts +1 -1
- package/dist/services/runtime/instance.js +1 -1
- package/dist/services/runtime/instance.js.map +1 -1
- package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
- package/dist/services/runtime/types.d.ts +31 -0
- package/dist/services/setup-manager.js +93 -18
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/suggestions.js.map +1 -1
- package/dist/services/webdav/server.d.ts +24 -0
- package/dist/services/webdav/server.js +420 -0
- package/dist/services/webdav/server.js.map +1 -0
- package/dist/services/webdav/xml-builder.d.ts +73 -0
- package/dist/services/webdav/xml-builder.js +156 -0
- package/dist/services/webdav/xml-builder.js.map +1 -0
- package/dist/services/workspace-builder.d.ts +29 -0
- package/dist/services/workspace-builder.js +188 -0
- package/dist/services/workspace-builder.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/utils/path-locks.d.ts +30 -0
- package/dist/utils/path-locks.js +63 -0
- package/dist/utils/path-locks.js.map +1 -0
- package/dist/utils/path-safety.d.ts +41 -0
- package/dist/utils/path-safety.js +119 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/safe-write.d.ts +24 -0
- package/dist/utils/safe-write.js +82 -0
- package/dist/utils/safe-write.js.map +1 -0
- package/package.json +16 -1
- package/public/assets/Dashboard-BdWPtroF.js +1 -0
- package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-B_2HlVBQ.js} +1 -1
- package/public/assets/{HermesConfigForm-anDnwUp_.js → HermesConfigForm-DVlhg3WV.js} +2 -2
- package/public/assets/{InitPassword-ZU9_-hDr.js → InitPassword-D7glTExX.js} +1 -1
- package/public/assets/InstanceDetail-CxSy2cpe.js +92 -0
- package/public/assets/{Login-BItXqYAJ.js → Login-Cfr5c2sv.js} +1 -1
- package/public/assets/NewInstance-BIYDmJis.js +1 -0
- package/public/assets/{ProviderRecommendations-DFYj7Fb6.js → ProviderRecommendations-BuRnvRcI.js} +1 -1
- package/public/assets/{Settings-Bttc6QmM.js → Settings-Cc-tYBil.js} +1 -1
- package/public/assets/{Setup-Bsxx1zgj.js → Setup-lGZEk5jq.js} +1 -1
- package/public/assets/{WeixinLoginPanel-DPZpAKgO.js → WeixinLoginPanel-CoGqzxeV.js} +2 -2
- package/public/assets/index-87IJXG-w.css +1 -0
- package/public/assets/index-BZc5zH7u.js +19 -0
- package/public/assets/{registry-5s2UB6is.js → registry-BWnkJgZ1.js} +2 -2
- package/public/assets/{usePolling-Do5Erqm_.js → usePolling-CwwT9KrC.js} +1 -1
- package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-y9V7Sfuu.js} +1 -1
- package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-BWrEVJVb.js} +6 -6
- package/public/index.html +4 -4
- package/scripts/check-app-spec.mjs +18 -4
- package/scripts/check-new-file-tests.mjs +230 -0
- package/scripts/check-quarantine-expiry.mjs +105 -0
- package/scripts/perf/README.md +49 -0
- package/scripts/perf/auth.js +99 -0
- package/scripts/perf/config.js +63 -0
- package/scripts/perf/instances.js +143 -0
- package/scripts/perf/proxy.js +96 -0
- package/scripts/smoke/files-w1.sh +142 -0
- package/scripts/smoke-backend.mjs +122 -0
- package/scripts/smoke-post-publish.mjs +346 -0
- package/public/assets/Dashboard-rkWp-CXd.js +0 -1
- package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
- package/public/assets/NewInstance-BousE6kY.js +0 -1
- package/public/assets/index-8xZy1z5k.css +0 -1
- package/public/assets/index-Dw3HhUYE.js +0 -19
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml-builder.js","sourceRoot":"","sources":["../../../src/services/webdav/xml-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FileMount } from "./runtime/types.js";
|
|
2
|
+
export declare class WorkspaceBuilderError extends Error {
|
|
3
|
+
reason: string;
|
|
4
|
+
constructor(message: string, reason: string);
|
|
5
|
+
}
|
|
6
|
+
export interface RebuildWorkspaceArgs {
|
|
7
|
+
/** Absolute host path of the instance's openclaw-home directory. */
|
|
8
|
+
openclawHome: string;
|
|
9
|
+
/** Absolute host path of FILES_ROOT (~/.jishushell/files). */
|
|
10
|
+
filesRoot: string;
|
|
11
|
+
/** Validated mount set; pass through validateMounts before calling. */
|
|
12
|
+
mounts: FileMount[];
|
|
13
|
+
/** Owning instance — used by isolation rule. */
|
|
14
|
+
instanceId: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RebuildResult {
|
|
17
|
+
/** What kind of workspace we produced. */
|
|
18
|
+
layout: "named" | "root" | "empty";
|
|
19
|
+
/** For "named", the alias → resolved abs target map for audit. */
|
|
20
|
+
symlinks: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
export declare function rebuildWorkspace(args: RebuildWorkspaceArgs): RebuildResult;
|
|
23
|
+
/**
|
|
24
|
+
* For audit / UI. Given the live workspace, report the layout in
|
|
25
|
+
* structural form without touching it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function describeWorkspace(openclawHome: string): RebuildResult & {
|
|
28
|
+
exists: boolean;
|
|
29
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the workspace symlink layout for an instance (M1 W2 PR-2).
|
|
3
|
+
*
|
|
4
|
+
* Given a validated FileMount[] and the host paths involved, this module
|
|
5
|
+
* (re)creates `${openclawHome}/workspace/` so that:
|
|
6
|
+
*
|
|
7
|
+
* - For named mounts: workspace is a real directory whose entries
|
|
8
|
+
* are alias symlinks → FILES_ROOT/{mount.path}
|
|
9
|
+
*
|
|
10
|
+
* - For a single root mount: workspace itself is a symlink →
|
|
11
|
+
* FILES_ROOT/{mount.path}, replacing the directory wholesale
|
|
12
|
+
*
|
|
13
|
+
* Why we don't touch `${openclawHome}/.openclaw/workspace/`:
|
|
14
|
+
* That subtree is OpenClaw's private state directory (skills config,
|
|
15
|
+
* sessions, mcporter.json). We give the user-facing mount surface a
|
|
16
|
+
* separate top-level directory `workspace/` directly under HOME,
|
|
17
|
+
* keeping the two namespaces strictly disjoint.
|
|
18
|
+
*
|
|
19
|
+
* Safety contract — refuses to clobber unknown content:
|
|
20
|
+
* - If workspace/ already contains plain files or non-symlink dirs
|
|
21
|
+
* (i.e. data we don't recognize), throw rather than touch.
|
|
22
|
+
* - Existing symlinks ARE removed — they were placed by a prior
|
|
23
|
+
* rebuildWorkspace call.
|
|
24
|
+
* - This means a *one-time legacy migration* (PR-7) must run first
|
|
25
|
+
* for instances whose workspace currently contains real data.
|
|
26
|
+
*/
|
|
27
|
+
import * as fs from "node:fs";
|
|
28
|
+
import * as path from "node:path";
|
|
29
|
+
import { validateMounts, effectiveAlias, ensureMountTargets, } from "./files-mounts.js";
|
|
30
|
+
export class WorkspaceBuilderError extends Error {
|
|
31
|
+
reason;
|
|
32
|
+
constructor(message, reason) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.reason = reason;
|
|
35
|
+
this.name = "WorkspaceBuilderError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const WORKSPACE_DIR_NAME = "workspace";
|
|
39
|
+
export function rebuildWorkspace(args) {
|
|
40
|
+
const { openclawHome, filesRoot, mounts, instanceId } = args;
|
|
41
|
+
// Validate up front — caller may have skipped this; we are the last
|
|
42
|
+
// line of defense before fs writes.
|
|
43
|
+
const v = validateMounts(mounts, instanceId);
|
|
44
|
+
if (!v.ok) {
|
|
45
|
+
const summary = v.errors.slice(0, 5).map((e) => e.message).join("; ");
|
|
46
|
+
throw new WorkspaceBuilderError(`mount set is invalid: ${summary}`, "invalid-mounts");
|
|
47
|
+
}
|
|
48
|
+
// Make sure all mount targets exist on the host so docker can bind
|
|
49
|
+
// them and so symlinks resolve.
|
|
50
|
+
ensureMountTargets(filesRoot, mounts);
|
|
51
|
+
if (!fs.existsSync(openclawHome)) {
|
|
52
|
+
fs.mkdirSync(openclawHome, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
const ws = path.join(openclawHome, WORKSPACE_DIR_NAME);
|
|
55
|
+
// Resolve mode: empty list → empty workspace; one root mount →
|
|
56
|
+
// root layout; otherwise named.
|
|
57
|
+
if (mounts.length === 0) {
|
|
58
|
+
clearWorkspace(ws);
|
|
59
|
+
return { layout: "empty", symlinks: {} };
|
|
60
|
+
}
|
|
61
|
+
const rootMount = mounts.find((m) => m.alias === null || m.alias === "");
|
|
62
|
+
if (rootMount) {
|
|
63
|
+
// Root mode is exclusive (validateMounts has already enforced
|
|
64
|
+
// that mounts.length === 1). Workspace itself becomes a symlink.
|
|
65
|
+
const target = path.join(filesRoot, rootMount.path);
|
|
66
|
+
replaceWithSymlink(ws, target);
|
|
67
|
+
return { layout: "root", symlinks: { "": target } };
|
|
68
|
+
}
|
|
69
|
+
// Named mode: workspace is a real directory; each entry is a symlink.
|
|
70
|
+
const symlinks = {};
|
|
71
|
+
if (fs.existsSync(ws)) {
|
|
72
|
+
const lst = fs.lstatSync(ws);
|
|
73
|
+
if (lst.isSymbolicLink()) {
|
|
74
|
+
// Was previously root-mode; remove and recreate as dir
|
|
75
|
+
fs.unlinkSync(ws);
|
|
76
|
+
fs.mkdirSync(ws);
|
|
77
|
+
}
|
|
78
|
+
else if (lst.isDirectory()) {
|
|
79
|
+
pruneSymlinkChildren(ws);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
throw new WorkspaceBuilderError(`workspace at ${ws} is a file; refusing to clobber`, "non-directory");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
fs.mkdirSync(ws);
|
|
87
|
+
}
|
|
88
|
+
for (const m of mounts) {
|
|
89
|
+
const alias = effectiveAlias(m);
|
|
90
|
+
if (alias === null)
|
|
91
|
+
continue; // shouldn't happen here (rootMount filtered)
|
|
92
|
+
const target = path.join(filesRoot, m.path);
|
|
93
|
+
const linkPath = path.join(ws, alias);
|
|
94
|
+
fs.symlinkSync(target, linkPath);
|
|
95
|
+
symlinks[alias] = target;
|
|
96
|
+
}
|
|
97
|
+
return { layout: "named", symlinks };
|
|
98
|
+
}
|
|
99
|
+
function clearWorkspace(ws) {
|
|
100
|
+
if (!fs.existsSync(ws)) {
|
|
101
|
+
fs.mkdirSync(ws, { recursive: true });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const lst = fs.lstatSync(ws);
|
|
105
|
+
if (lst.isSymbolicLink()) {
|
|
106
|
+
fs.unlinkSync(ws);
|
|
107
|
+
fs.mkdirSync(ws);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (lst.isDirectory()) {
|
|
111
|
+
pruneSymlinkChildren(ws);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
throw new WorkspaceBuilderError(`workspace at ${ws} is a file; refusing to clobber`, "non-directory");
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Remove every entry under ws/ that is a symlink. Refuse if any entry
|
|
118
|
+
* is a plain file or non-symlink directory — that means a previous
|
|
119
|
+
* version had real data here that hasn't been migrated yet (PR-7).
|
|
120
|
+
*/
|
|
121
|
+
function pruneSymlinkChildren(ws) {
|
|
122
|
+
for (const name of fs.readdirSync(ws)) {
|
|
123
|
+
const full = path.join(ws, name);
|
|
124
|
+
const lst = fs.lstatSync(full);
|
|
125
|
+
if (lst.isSymbolicLink()) {
|
|
126
|
+
fs.unlinkSync(full);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
throw new WorkspaceBuilderError(`workspace contains non-symlink entry ${name}; legacy migration required before mount changes`, "needs-migration");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Replace `target` with a symlink to `linkTarget`, safe against existing
|
|
134
|
+
* symlinks (atomic via unlink+symlinkSync). Symlink children of an
|
|
135
|
+
* existing directory are pruned first; plain files trigger the legacy
|
|
136
|
+
* migration error. */
|
|
137
|
+
function replaceWithSymlink(target, linkTarget) {
|
|
138
|
+
if (fs.existsSync(target)) {
|
|
139
|
+
const lst = fs.lstatSync(target);
|
|
140
|
+
if (lst.isSymbolicLink()) {
|
|
141
|
+
fs.unlinkSync(target);
|
|
142
|
+
}
|
|
143
|
+
else if (lst.isDirectory()) {
|
|
144
|
+
// Reuse the same prune-or-throw policy as named mode so the
|
|
145
|
+
// named → root transition is symmetric with named → named.
|
|
146
|
+
pruneSymlinkChildren(target);
|
|
147
|
+
// After pruning, dir must be empty — otherwise pruneSymlinkChildren
|
|
148
|
+
// would have thrown.
|
|
149
|
+
fs.rmdirSync(target);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
throw new WorkspaceBuilderError(`workspace at ${target} is a regular file; refusing to clobber`, "non-directory");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
fs.symlinkSync(linkTarget, target);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* For audit / UI. Given the live workspace, report the layout in
|
|
159
|
+
* structural form without touching it.
|
|
160
|
+
*/
|
|
161
|
+
export function describeWorkspace(openclawHome) {
|
|
162
|
+
const ws = path.join(openclawHome, WORKSPACE_DIR_NAME);
|
|
163
|
+
if (!fs.existsSync(ws)) {
|
|
164
|
+
return { exists: false, layout: "empty", symlinks: {} };
|
|
165
|
+
}
|
|
166
|
+
const lst = fs.lstatSync(ws);
|
|
167
|
+
if (lst.isSymbolicLink()) {
|
|
168
|
+
const target = fs.readlinkSync(ws);
|
|
169
|
+
return { exists: true, layout: "root", symlinks: { "": target } };
|
|
170
|
+
}
|
|
171
|
+
if (!lst.isDirectory()) {
|
|
172
|
+
return { exists: true, layout: "empty", symlinks: {} };
|
|
173
|
+
}
|
|
174
|
+
const symlinks = {};
|
|
175
|
+
for (const name of fs.readdirSync(ws)) {
|
|
176
|
+
const full = path.join(ws, name);
|
|
177
|
+
const childLst = fs.lstatSync(full);
|
|
178
|
+
if (!childLst.isSymbolicLink())
|
|
179
|
+
continue;
|
|
180
|
+
symlinks[name] = fs.readlinkSync(full);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
exists: true,
|
|
184
|
+
layout: Object.keys(symlinks).length === 0 ? "empty" : "named",
|
|
185
|
+
symlinks,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=workspace-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-builder.js","sourceRoot":"","sources":["../../src/services/workspace-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EACL,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACV;IAApC,YAAY,OAAe,EAAS,MAAc;QAChD,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,WAAM,GAAN,MAAM,CAAQ;QAEhD,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAoBD,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAEvC,MAAM,UAAU,gBAAgB,CAC9B,IAA0B;IAE1B,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAE7D,oEAAoE;IACpE,oCAAoC;IACpC,MAAM,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC7C,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACV,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtE,MAAM,IAAI,qBAAqB,CAC7B,yBAAyB,OAAO,EAAE,EAClC,gBAAgB,CACjB,CAAC;IACJ,CAAC;IAED,mEAAmE;IACnE,gCAAgC;IAChC,kBAAkB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAEtC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;IAEvD,+DAA+D;IAC/D,gCAAgC;IAChC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,cAAc,CAAC,EAAE,CAAC,CAAC;QACnB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAC3B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,EAAE,CAC1C,CAAC;IAEF,IAAI,SAAS,EAAE,CAAC;QACd,8DAA8D;QAC9D,iEAAiE;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACpD,kBAAkB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC;IACtD,CAAC;IAED,sEAAsE;IACtE,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAE5C,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC;YACzB,uDAAuD;YACvD,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAClB,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;aAAM,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;YAC7B,oBAAoB,CAAC,EAAE,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,qBAAqB,CAC7B,gBAAgB,EAAE,iCAAiC,EACnD,eAAe,CAChB,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS,CAAC,6CAA6C;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACtC,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACjC,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;IAC3B,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACvC,CAAC;AAED,SAAS,cAAc,CAAC,EAAU;IAChC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;QACvB,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC7B,IAAI,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC;QACzB,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAClB,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACjB,OAAO;IACT,CAAC;IACD,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;QACtB,oBAAoB,CAAC,EAAE,CAAC,CAAC;QACzB,OAAO;IACT,CAAC;IACD,MAAM,IAAI,qBAAqB,CAC7B,gBAAgB,EAAE,iCAAiC,EACnD,eAAe,CAChB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,EAAU;IACtC,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC;YACzB,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,qBAAqB,CAC7B,wCAAwC,IAAI,kDAAkD,EAC9F,iBAAiB,CAClB,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;uBAGuB;AACvB,SAAS,kBAAkB,CAAC,MAAc,EAAE,UAAkB;IAC5D,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC;YACzB,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;aAAM,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;YAC7B,4DAA4D;YAC5D,2DAA2D;YAC3D,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAC7B,oEAAoE;YACpE,qBAAqB;YACrB,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,qBAAqB,CAC7B,gBAAgB,MAAM,yCAAyC,EAC/D,eAAe,CAChB,CAAC;QACJ,CAAC;IACH,CAAC;IACD,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,YAAoB;IAEpB,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1D,CAAC;IACD,MAAM,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC7B,IAAI,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE;YAAE,SAAS;QACzC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;QAC9D,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -111,6 +111,25 @@ export interface PanelConfig {
|
|
|
111
111
|
};
|
|
112
112
|
telemetry?: false;
|
|
113
113
|
activation_reported?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* External mount points — let any host directory appear under
|
|
116
|
+
* ~/.jishushell/files/ as a virtual subtree. The user picks the
|
|
117
|
+
* alias; host_path can be any directory the panel user can read
|
|
118
|
+
* (sensitive system directories are flagged but allowed if the
|
|
119
|
+
* user insists). Mode "ro" forbids any write through the panel
|
|
120
|
+
* or WebDAV; "rw" allows it.
|
|
121
|
+
*
|
|
122
|
+
* Implementation: the panel does NOT create a real symlink in
|
|
123
|
+
* files/ — it virtualizes the path at the FilesManager layer.
|
|
124
|
+
* That avoids the symlink-defense conflict and works identically
|
|
125
|
+
* across docker / raw_exec / process modes.
|
|
126
|
+
*/
|
|
127
|
+
external_mounts?: Array<{
|
|
128
|
+
alias: string;
|
|
129
|
+
host_path: string;
|
|
130
|
+
mode: "ro" | "rw";
|
|
131
|
+
description?: string;
|
|
132
|
+
}>;
|
|
114
133
|
[key: string]: any;
|
|
115
134
|
}
|
|
116
135
|
export type AppTaskRuntime = "container" | "process" | "vm";
|
|
@@ -126,6 +145,16 @@ export interface AppTaskPort {
|
|
|
126
145
|
host_port?: number;
|
|
127
146
|
container_port?: number;
|
|
128
147
|
visibility?: AppTaskPortVisibility;
|
|
148
|
+
/**
|
|
149
|
+
* Name of a Nomad `host_network` block declared in nomad.hcl. Default is
|
|
150
|
+
* "external" (eth0 / primary LAN interface). Use "docker_bridge" to
|
|
151
|
+
* publish on the docker0 IP (172.17.0.1) — required when peer containers
|
|
152
|
+
* in the same group dial this port via `host.docker.internal`. Without
|
|
153
|
+
* this, Nomad picks the wrong interface and cross-task
|
|
154
|
+
* host.docker.internal calls get "connection refused" (e.g. weknora-app
|
|
155
|
+
* → paradedb on port 18093).
|
|
156
|
+
*/
|
|
157
|
+
host_network?: string;
|
|
129
158
|
}
|
|
130
159
|
export interface AppTaskHealth {
|
|
131
160
|
http?: {
|
|
@@ -173,6 +202,19 @@ export interface AppTask {
|
|
|
173
202
|
* the unified builder falls back to "host" by default.
|
|
174
203
|
*/
|
|
175
204
|
user?: string;
|
|
205
|
+
/**
|
|
206
|
+
* Linux capabilities to add back on top of the runtime baseline
|
|
207
|
+
* (`cap_drop: ["ALL"]`). Validated against a tight allowlist
|
|
208
|
+
* (CHOWN / DAC_OVERRIDE / FOWNER / SETUID / SETGID / SETPCAP /
|
|
209
|
+
* NET_BIND_SERVICE) before passing to the docker driver. Required by
|
|
210
|
+
* canonical postgres/redis/-style images whose entrypoint runs as root
|
|
211
|
+
* and uses `gosu`+`chown` to drop privileges to a non-root user before
|
|
212
|
+
* exec'ing the main process. The container still drops to a non-root
|
|
213
|
+
* user before serving traffic — this only re-arms the kernel facilities
|
|
214
|
+
* the drop step needs. Caps outside the allowlist are silently dropped
|
|
215
|
+
* (fail-closed).
|
|
216
|
+
*/
|
|
217
|
+
cap_add?: string[];
|
|
176
218
|
}
|
|
177
219
|
export interface AppTerminalCommandPreset {
|
|
178
220
|
cmd: string;
|
|
@@ -232,6 +274,24 @@ export interface AppProvide {
|
|
|
232
274
|
* should authenticate when calling the endpoint. Optional. Absent = no auth.
|
|
233
275
|
*/
|
|
234
276
|
auth?: AppProvideAuth;
|
|
277
|
+
/**
|
|
278
|
+
* Controls how the instance detail page generates the iframe `src` for
|
|
279
|
+
* this capability:
|
|
280
|
+
*
|
|
281
|
+
* - `"auto"` (default): direct LAN URL when the published port listens
|
|
282
|
+
* on a non-loopback interface, fall back to panel proxy when only
|
|
283
|
+
* loopback. Best for apps users typically reach by IP+port.
|
|
284
|
+
* - `"proxy"`: always use the panel's same-origin reverse proxy path
|
|
285
|
+
* (`/api/instances/:id/provides/:capability/`). Needed when the
|
|
286
|
+
* upstream is on a network the browser can't dial directly (corp
|
|
287
|
+
* firewall blocks high ports, VPN, mixed-content boundary) or when
|
|
288
|
+
* the upstream emits `X-Frame-Options: SAMEORIGIN`/CSP that block
|
|
289
|
+
* cross-origin iframe embedding — the proxy strips those headers.
|
|
290
|
+
* - `"direct"`: always emit the direct LAN URL, never the proxy path.
|
|
291
|
+
* Useful for upstreams that can't safely run behind a sub-path
|
|
292
|
+
* reverse proxy (some SPAs hardcode absolute asset URLs).
|
|
293
|
+
*/
|
|
294
|
+
embedded?: "auto" | "proxy" | "direct";
|
|
235
295
|
}
|
|
236
296
|
/**
|
|
237
297
|
* §6 — describes how a consumer that binds this `provides[]` capability
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path-scoped advisory locks for the Files module (W1 PR-2).
|
|
3
|
+
*
|
|
4
|
+
* Single-process serialization: all callers using the same `path` string run
|
|
5
|
+
* in arrival order; different paths run independently in parallel.
|
|
6
|
+
*
|
|
7
|
+
* This is *advisory* — it only protects callers that opt in via withPathLock.
|
|
8
|
+
* It does not protect against external writers (Agent, WebDAV, ssh) that
|
|
9
|
+
* touch the file directly. Those paths have their own atomicity (POSIX
|
|
10
|
+
* rename) and the application's tolerance is "POSIX is source of truth".
|
|
11
|
+
*
|
|
12
|
+
* Stale locks (> 60s) are logged but not broken — correctness over recovery.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Run `fn` while holding an advisory lock on `path`.
|
|
16
|
+
*
|
|
17
|
+
* If another caller holds the lock, this call queues behind it; the queue
|
|
18
|
+
* preserves arrival order.
|
|
19
|
+
*
|
|
20
|
+
* Throws (or resolves) propagate from `fn`. The lock is always released.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withPathLock<T>(path: string, fn: () => Promise<T>): Promise<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Test-only: clear all locks. Do not use in production.
|
|
25
|
+
*/
|
|
26
|
+
export declare function _clearAllPathLocks(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Test-only: number of active locks. Do not use in production.
|
|
29
|
+
*/
|
|
30
|
+
export declare function _activePathLockCount(): number;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path-scoped advisory locks for the Files module (W1 PR-2).
|
|
3
|
+
*
|
|
4
|
+
* Single-process serialization: all callers using the same `path` string run
|
|
5
|
+
* in arrival order; different paths run independently in parallel.
|
|
6
|
+
*
|
|
7
|
+
* This is *advisory* — it only protects callers that opt in via withPathLock.
|
|
8
|
+
* It does not protect against external writers (Agent, WebDAV, ssh) that
|
|
9
|
+
* touch the file directly. Those paths have their own atomicity (POSIX
|
|
10
|
+
* rename) and the application's tolerance is "POSIX is source of truth".
|
|
11
|
+
*
|
|
12
|
+
* Stale locks (> 60s) are logged but not broken — correctness over recovery.
|
|
13
|
+
*/
|
|
14
|
+
const locks = new Map();
|
|
15
|
+
const STALE_LOCK_MS = 60_000;
|
|
16
|
+
/**
|
|
17
|
+
* Run `fn` while holding an advisory lock on `path`.
|
|
18
|
+
*
|
|
19
|
+
* If another caller holds the lock, this call queues behind it; the queue
|
|
20
|
+
* preserves arrival order.
|
|
21
|
+
*
|
|
22
|
+
* Throws (or resolves) propagate from `fn`. The lock is always released.
|
|
23
|
+
*/
|
|
24
|
+
export async function withPathLock(path, fn) {
|
|
25
|
+
const existing = locks.get(path);
|
|
26
|
+
if (existing && Date.now() - existing.startedAt > STALE_LOCK_MS) {
|
|
27
|
+
console.warn(`[path-lock] stale lock detected for ${path} (held ${Date.now() - existing.startedAt}ms)`);
|
|
28
|
+
}
|
|
29
|
+
const previous = existing?.promise ?? Promise.resolve();
|
|
30
|
+
let release;
|
|
31
|
+
const next = new Promise((resolve) => {
|
|
32
|
+
release = resolve;
|
|
33
|
+
});
|
|
34
|
+
// Our slot in the chain: wait for previous holder, then release `next`
|
|
35
|
+
const ourPromise = previous.then(() => next);
|
|
36
|
+
const entry = { promise: ourPromise, startedAt: Date.now() };
|
|
37
|
+
locks.set(path, entry);
|
|
38
|
+
try {
|
|
39
|
+
await previous;
|
|
40
|
+
// Re-stamp startedAt to "actual hold start" for stale detection
|
|
41
|
+
entry.startedAt = Date.now();
|
|
42
|
+
return await fn();
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
release();
|
|
46
|
+
if (locks.get(path) === entry) {
|
|
47
|
+
locks.delete(path);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Test-only: clear all locks. Do not use in production.
|
|
53
|
+
*/
|
|
54
|
+
export function _clearAllPathLocks() {
|
|
55
|
+
locks.clear();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Test-only: number of active locks. Do not use in production.
|
|
59
|
+
*/
|
|
60
|
+
export function _activePathLockCount() {
|
|
61
|
+
return locks.size;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=path-locks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-locks.js","sourceRoot":"","sources":["../../src/utils/path-locks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;AAC3C,MAAM,aAAa,GAAG,MAAM,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAY,EACZ,EAAoB;IAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,SAAS,GAAG,aAAa,EAAE,CAAC;QAChE,OAAO,CAAC,IAAI,CACV,uCAAuC,IAAI,UACzC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,SACxB,KAAK,CACN,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,EAAE,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAExD,IAAI,OAAoB,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACzC,OAAO,GAAG,OAAO,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,uEAAuE;IACvE,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAc,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACxE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAEvB,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC;QACf,gEAAgE;QAChE,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,OAAO,EAAE,CAAC;QACV,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC;YAC9B,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare class PathSafetyError extends Error {
|
|
2
|
+
reason: string;
|
|
3
|
+
constructor(message: string, reason: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Validate a user-provided relative path string against common attack
|
|
7
|
+
* vectors. Performs no IO.
|
|
8
|
+
*
|
|
9
|
+
* Accepted:
|
|
10
|
+
* - "" (root)
|
|
11
|
+
* - "notes/x.md", "深/文件.txt", "with-dashes_and.dots"
|
|
12
|
+
*
|
|
13
|
+
* Rejected (PathSafetyError):
|
|
14
|
+
* - leading "/" (absolute)
|
|
15
|
+
* - drive letters ("C:\\foo", "Z:foo")
|
|
16
|
+
* - any "..", ".", or all-dot ("...", "....") segment
|
|
17
|
+
* - empty segments ("a//b")
|
|
18
|
+
* - leading or trailing "/"
|
|
19
|
+
* - backslash anywhere
|
|
20
|
+
* - null bytes or control chars (< 0x20)
|
|
21
|
+
* - segments ending in "." or " " (Windows quirk; some sync tools misbehave)
|
|
22
|
+
* - segment > 255 bytes (ext4 NAME_MAX)
|
|
23
|
+
* - total > 4096 bytes (Linux PATH_MAX)
|
|
24
|
+
* - non-string input
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateRelativePath(input: unknown): void;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a validated relative path inside a fixed root, then verify the
|
|
29
|
+
* resolved path is still inside that root using a separator-explicit prefix
|
|
30
|
+
* check (so `/foo` does not match `/foobar`).
|
|
31
|
+
*
|
|
32
|
+
* Performs no IO. Symlink resolution is the caller's responsibility.
|
|
33
|
+
*
|
|
34
|
+
* @throws PathSafetyError on validation failure or escape attempt
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveSafe(root: string, rel: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Returns true if any segment of the path begins with ".".
|
|
39
|
+
* Used to optionally hide dotfiles in directory listings.
|
|
40
|
+
*/
|
|
41
|
+
export declare function isHidden(input: string): boolean;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path safety utilities for the Files module (W1).
|
|
3
|
+
*
|
|
4
|
+
* Three layers of defense (per .omc/plans/files-w1.md § 7.1):
|
|
5
|
+
* 1. validateRelativePath — string-level checks (this module, no IO)
|
|
6
|
+
* 2. resolveSafe — path.resolve + prefix check (this module, no IO)
|
|
7
|
+
* 3. realpath/symlink — performed by FilesManager (defense 3, IO)
|
|
8
|
+
*
|
|
9
|
+
* Empty string is the conventional root path and is accepted by both functions.
|
|
10
|
+
*/
|
|
11
|
+
import { resolve, sep } from "path";
|
|
12
|
+
const PATH_MAX_BYTES = 4096;
|
|
13
|
+
const NAME_MAX_BYTES = 255;
|
|
14
|
+
export class PathSafetyError extends Error {
|
|
15
|
+
reason;
|
|
16
|
+
constructor(message, reason) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.reason = reason;
|
|
19
|
+
this.name = "PathSafetyError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate a user-provided relative path string against common attack
|
|
24
|
+
* vectors. Performs no IO.
|
|
25
|
+
*
|
|
26
|
+
* Accepted:
|
|
27
|
+
* - "" (root)
|
|
28
|
+
* - "notes/x.md", "深/文件.txt", "with-dashes_and.dots"
|
|
29
|
+
*
|
|
30
|
+
* Rejected (PathSafetyError):
|
|
31
|
+
* - leading "/" (absolute)
|
|
32
|
+
* - drive letters ("C:\\foo", "Z:foo")
|
|
33
|
+
* - any "..", ".", or all-dot ("...", "....") segment
|
|
34
|
+
* - empty segments ("a//b")
|
|
35
|
+
* - leading or trailing "/"
|
|
36
|
+
* - backslash anywhere
|
|
37
|
+
* - null bytes or control chars (< 0x20)
|
|
38
|
+
* - segments ending in "." or " " (Windows quirk; some sync tools misbehave)
|
|
39
|
+
* - segment > 255 bytes (ext4 NAME_MAX)
|
|
40
|
+
* - total > 4096 bytes (Linux PATH_MAX)
|
|
41
|
+
* - non-string input
|
|
42
|
+
*/
|
|
43
|
+
export function validateRelativePath(input) {
|
|
44
|
+
if (typeof input !== "string") {
|
|
45
|
+
throw new PathSafetyError("path must be a string", "type");
|
|
46
|
+
}
|
|
47
|
+
if (input === "")
|
|
48
|
+
return; // root, ok
|
|
49
|
+
if (Buffer.byteLength(input, "utf8") > PATH_MAX_BYTES) {
|
|
50
|
+
throw new PathSafetyError("path too long", "path-length");
|
|
51
|
+
}
|
|
52
|
+
if (input.startsWith("/")) {
|
|
53
|
+
throw new PathSafetyError("path must be relative (no leading /)", "absolute");
|
|
54
|
+
}
|
|
55
|
+
if (input.includes("\\")) {
|
|
56
|
+
throw new PathSafetyError("backslash not allowed", "backslash");
|
|
57
|
+
}
|
|
58
|
+
// Windows drive letter: e.g. "C:\\foo", "Z:foo", "Z:/foo"
|
|
59
|
+
if (/^[A-Za-z]:/.test(input)) {
|
|
60
|
+
throw new PathSafetyError("drive letter not allowed", "drive-letter");
|
|
61
|
+
}
|
|
62
|
+
for (let i = 0; i < input.length; i++) {
|
|
63
|
+
const code = input.charCodeAt(i);
|
|
64
|
+
if (code === 0) {
|
|
65
|
+
throw new PathSafetyError("null byte not allowed", "null-byte");
|
|
66
|
+
}
|
|
67
|
+
if (code < 0x20) {
|
|
68
|
+
throw new PathSafetyError("control character not allowed", "control-char");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (input.endsWith("/")) {
|
|
72
|
+
throw new PathSafetyError("path must not end with /", "trailing-slash");
|
|
73
|
+
}
|
|
74
|
+
const segments = input.split("/");
|
|
75
|
+
for (const seg of segments) {
|
|
76
|
+
if (seg === "") {
|
|
77
|
+
throw new PathSafetyError("empty path segment", "empty-segment");
|
|
78
|
+
}
|
|
79
|
+
// Reject "." ".." and any all-dots variant ("..." "...." etc.)
|
|
80
|
+
if (/^\.+$/.test(seg)) {
|
|
81
|
+
throw new PathSafetyError(`all-dots segment "${seg}" not allowed`, "dot-segment");
|
|
82
|
+
}
|
|
83
|
+
if (Buffer.byteLength(seg, "utf8") > NAME_MAX_BYTES) {
|
|
84
|
+
throw new PathSafetyError("path segment too long", "segment-length");
|
|
85
|
+
}
|
|
86
|
+
if (seg.endsWith(".") || seg.endsWith(" ")) {
|
|
87
|
+
throw new PathSafetyError(`segment ends with "." or space`, "windows-quirk");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve a validated relative path inside a fixed root, then verify the
|
|
93
|
+
* resolved path is still inside that root using a separator-explicit prefix
|
|
94
|
+
* check (so `/foo` does not match `/foobar`).
|
|
95
|
+
*
|
|
96
|
+
* Performs no IO. Symlink resolution is the caller's responsibility.
|
|
97
|
+
*
|
|
98
|
+
* @throws PathSafetyError on validation failure or escape attempt
|
|
99
|
+
*/
|
|
100
|
+
export function resolveSafe(root, rel) {
|
|
101
|
+
validateRelativePath(rel);
|
|
102
|
+
const resolved = resolve(root, rel);
|
|
103
|
+
const rootWithSep = root.endsWith(sep) ? root : root + sep;
|
|
104
|
+
const rootNoSep = root.endsWith(sep) ? root.slice(0, -1) : root;
|
|
105
|
+
if (resolved !== rootNoSep && !resolved.startsWith(rootWithSep)) {
|
|
106
|
+
throw new PathSafetyError("path escapes root", "escape");
|
|
107
|
+
}
|
|
108
|
+
return resolved;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns true if any segment of the path begins with ".".
|
|
112
|
+
* Used to optionally hide dotfiles in directory listings.
|
|
113
|
+
*/
|
|
114
|
+
export function isHidden(input) {
|
|
115
|
+
if (input === "")
|
|
116
|
+
return false;
|
|
117
|
+
return input.split("/").some(s => s.startsWith("."));
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=path-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-safety.js","sourceRoot":"","sources":["../../src/utils/path-safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
export interface AtomicWriteOptions {
|
|
3
|
+
/** Compute streaming sha256 of the data while writing. Default: undefined */
|
|
4
|
+
hash?: "sha256";
|
|
5
|
+
/** Expected total size; if set and actual differs, throws. Default: no check */
|
|
6
|
+
expectedSize?: number;
|
|
7
|
+
/** Write mode (POSIX) of the final file. Default 0o644 */
|
|
8
|
+
mode?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface AtomicWriteResult {
|
|
11
|
+
size: number;
|
|
12
|
+
hash?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Stream `source` to `targetPath` atomically.
|
|
16
|
+
*
|
|
17
|
+
* Memory peak is the Node stream high-water-mark (~64KB), regardless of the
|
|
18
|
+
* total file size. Suitable for arbitrarily large files.
|
|
19
|
+
*/
|
|
20
|
+
export declare function atomicWriteStream(targetPath: string, source: Readable, opts?: AtomicWriteOptions): Promise<AtomicWriteResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Convenience wrapper for small Buffer writes. Uses the same atomic pipeline.
|
|
23
|
+
*/
|
|
24
|
+
export declare function safeWriteBuffer(targetPath: string, buf: Buffer, opts?: AtomicWriteOptions): Promise<AtomicWriteResult>;
|