jishushell 0.4.24 → 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/INSTALL-NOTICE +11 -0
- package/apps/anythingllm-container.yaml +287 -0
- package/apps/browserless-chromium-container.yaml +90 -0
- package/apps/filebrowser-container.yaml +163 -0
- package/apps/hermes-container.yaml +36 -2
- package/apps/ollama-binary.yaml +91 -90
- package/apps/ollama-cpu-container.yaml +8 -1
- package/apps/ollama-with-hollama-binary.yaml +91 -90
- package/apps/openclaw-binary.yaml +38 -1
- package/apps/openclaw-container.yaml +45 -2
- package/apps/openclaw-with-ollama-container.yaml +11 -2
- package/apps/openclaw-with-searxng-container.yaml +26 -2
- package/apps/openwebui-container.yaml +45 -1
- package/apps/playwright-container.yaml +7 -1
- package/apps/searxng-container.yaml +58 -7
- package/apps/weknora-container.yaml +471 -0
- package/dist/cli/app.js +79 -9
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/doctor.d.ts +12 -12
- package/dist/cli/doctor.js +242 -55
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/llm.d.ts +4 -3
- package/dist/cli/llm.js +4 -3
- package/dist/cli/llm.js.map +1 -1
- package/dist/cli/panel.d.ts +6 -5
- package/dist/cli/panel.js +10 -9
- 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/control.d.ts +7 -6
- package/dist/control.js +7 -6
- package/dist/control.js.map +1 -1
- package/dist/install.js +3 -3
- package/dist/install.js.map +1 -1
- package/dist/routes/agent-apps.d.ts +1 -1
- package/dist/routes/agent-apps.js +1 -1
- package/dist/routes/apps.js +44 -11
- package/dist/routes/apps.js.map +1 -1
- package/dist/routes/auth.js +5 -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 +826 -17
- 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/llm.js +24 -35
- package/dist/routes/llm.js.map +1 -1
- package/dist/routes/setup.js +10 -10
- 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.d.ts +9 -0
- package/dist/server.js +751 -20
- package/dist/server.js.map +1 -1
- package/dist/services/agent-apps/catalog.js +4 -3
- package/dist/services/agent-apps/catalog.js.map +1 -1
- package/dist/services/agent-apps/index.d.ts +1 -1
- package/dist/services/agent-apps/index.js +1 -1
- package/dist/services/agent-apps/installers/adapter.d.ts +1 -1
- package/dist/services/agent-apps/installers/adapter.js +1 -1
- package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
- package/dist/services/agent-apps/installers/shell-script.js +3 -3
- package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
- package/dist/services/agent-apps/types.d.ts +2 -2
- package/dist/services/agent-apps/types.js +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 +25 -1
- package/dist/services/app/app-manager.js +829 -150
- 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 +7 -4
- 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/provide-resolver.d.ts +29 -0
- package/dist/services/app/provide-resolver.js +112 -0
- package/dist/services/app/provide-resolver.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/capability-endpoint-validator.d.ts +41 -0
- package/dist/services/capability-endpoint-validator.js +104 -0
- package/dist/services/capability-endpoint-validator.js.map +1 -0
- package/dist/services/capability-health.d.ts +16 -0
- package/dist/services/capability-health.js +121 -0
- package/dist/services/capability-health.js.map +1 -0
- package/dist/services/capability-registry.d.ts +106 -0
- package/dist/services/capability-registry.js +313 -0
- package/dist/services/capability-registry.js.map +1 -0
- package/dist/services/connection-apply.d.ts +91 -0
- package/dist/services/connection-apply.js +475 -0
- package/dist/services/connection-apply.js.map +1 -0
- package/dist/services/connection-resolver.d.ts +65 -0
- package/dist/services/connection-resolver.js +281 -0
- package/dist/services/connection-resolver.js.map +1 -0
- package/dist/services/connection-transactor.d.ts +39 -0
- package/dist/services/connection-transactor.js +351 -0
- package/dist/services/connection-transactor.js.map +1 -0
- 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.d.ts +13 -0
- package/dist/services/instance-manager.js +138 -46
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +16 -2
- package/dist/services/llm-proxy/index.js +48 -44
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/probe.d.ts +6 -0
- package/dist/services/llm-proxy/probe.js +85 -0
- package/dist/services/llm-proxy/probe.js.map +1 -0
- package/dist/services/llm-proxy/ssrf.d.ts +1 -0
- package/dist/services/llm-proxy/ssrf.js +24 -9
- 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 +428 -35
- 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 +4 -3
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/runtime/adapters/hermes.d.ts +30 -1
- package/dist/services/runtime/adapters/hermes.js +219 -6
- package/dist/services/runtime/adapters/hermes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw-mcporter.d.ts +45 -0
- package/dist/services/runtime/adapters/openclaw-mcporter.js +108 -0
- package/dist/services/runtime/adapters/openclaw-mcporter.js.map +1 -0
- 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 +177 -0
- package/dist/services/runtime/adapters/openclaw.js +1171 -11
- 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/mcp-shims/firewall.d.ts +26 -0
- package/dist/services/runtime/mcp-shims/firewall.js +129 -0
- package/dist/services/runtime/mcp-shims/firewall.js.map +1 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.d.ts +27 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.js +125 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.d.ts +83 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js +127 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js.map +1 -0
- package/dist/services/runtime/migrations.d.ts +8 -0
- package/dist/services/runtime/migrations.js +100 -0
- package/dist/services/runtime/migrations.js.map +1 -1
- package/dist/services/runtime/types.d.ts +46 -0
- package/dist/services/setup-manager.js +99 -24
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/suggestions.d.ts +27 -0
- package/dist/services/suggestions.js +133 -0
- package/dist/services/suggestions.js.map +1 -0
- package/dist/services/task-registry.js +4 -2
- package/dist/services/task-registry.js.map +1 -1
- package/dist/services/telemetry/device-fingerprint.d.ts +1 -1
- package/dist/services/telemetry/device-fingerprint.js +1 -1
- package/dist/services/types-shim.d.ts +16 -0
- package/dist/services/types-shim.js +2 -0
- package/dist/services/types-shim.js.map +1 -0
- 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 +231 -1
- package/dist/utils/instance-lock.d.ts +22 -0
- package/dist/utils/instance-lock.js +48 -0
- package/dist/utils/instance-lock.js.map +1 -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-json.js +55 -22
- package/dist/utils/safe-json.js.map +1 -1
- 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/install/jishu-install.sh +323 -27
- package/install/jishu-uninstall.sh +353 -20
- package/package.json +18 -1
- package/public/assets/Dashboard-BdWPtroF.js +1 -0
- package/public/assets/{HermesChatPanel-mFSureyc.js → HermesChatPanel-B_2HlVBQ.js} +1 -1
- package/public/assets/HermesConfigForm-DVlhg3WV.js +4 -0
- package/public/assets/{InitPassword-CVA8wQA6.js → InitPassword-D7glTExX.js} +1 -1
- package/public/assets/InstanceDetail-CxSy2cpe.js +92 -0
- package/public/assets/{Login-BWsZH2mu.js → Login-Cfr5c2sv.js} +1 -1
- package/public/assets/NewInstance-BIYDmJis.js +1 -0
- package/public/assets/ProviderRecommendations-BuRnvRcI.js +1 -0
- package/public/assets/Settings-Cc-tYBil.js +1 -0
- package/public/assets/Setup-lGZEk5jq.js +1 -0
- package/public/assets/{WeixinLoginPanel-CnjR8xMu.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/providers-DtNXh9JD.js +1 -0
- package/public/assets/registry-BWnkJgZ1.js +2 -0
- 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 +457 -0
- package/scripts/check-i18n.mjs +154 -0
- 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/run.sh +4 -4
- 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-B-JoOjBQ.js +0 -1
- package/public/assets/HermesConfigForm-DvR05LK1.js +0 -4
- package/public/assets/InstanceDetail-DcZW2QGO.js +0 -91
- package/public/assets/NewInstance-BCIrAd86.js +0 -1
- package/public/assets/Settings-xkDcduFz.js +0 -1
- package/public/assets/Setup-Cfuwj4gV.js +0 -1
- package/public/assets/index-CPhVFEsx.css +0 -1
- package/public/assets/index-DQsM6Joa.js +0 -19
- package/public/assets/providers-V-vwrExZ.js +0 -1
- package/public/assets/registry-B4UFJdpA.js +0 -2
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connection-transactor — PUT /connections atomic 5-step flow (§10.3).
|
|
3
|
+
*
|
|
4
|
+
* 1. Validate — resolveConnections runtime mode + per-binding checks
|
|
5
|
+
* 2. Snapshot — capture instance.json plus the side-effect files that
|
|
6
|
+
* persist hooks may write through: provider.env (LLM
|
|
7
|
+
* proxy-upstream), mcporter.json (MCP),
|
|
8
|
+
* openclaw.json (LLM proxy-upstream + adapter
|
|
9
|
+
* applyConnectionEnv), and Hermes config.yaml / .env.
|
|
10
|
+
* Snapshot now covers OpenClaw (V2 + legacy),
|
|
11
|
+
* Hermes (V2 + legacy), and generic app-dir consumers.
|
|
12
|
+
* 3. Apply hooks — invoke PERSIST_HOOKS for each resolved binding in
|
|
13
|
+
* deterministic order (default-env → search → browser
|
|
14
|
+
* → llm → mcp). On any failure, jump to step 5.
|
|
15
|
+
* 4. Persist — read-modify-write instance.json: replace `connections`
|
|
16
|
+
* map only, keep other fields (notably the
|
|
17
|
+
* `connections-env` written by step 3 hooks for generic
|
|
18
|
+
* apps without an adapter).
|
|
19
|
+
* 5. Rollback — reverse-order recovery from snapshots: restore each
|
|
20
|
+
* side-effect file (or unlink if it didn't exist
|
|
21
|
+
* pre-apply) before restoring instance.json.
|
|
22
|
+
*
|
|
23
|
+
* Returns either { ok: true, resolved, pending } or a structured error.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
|
|
26
|
+
import { dirname, join } from "node:path";
|
|
27
|
+
import { INSTANCES_DIR, APPS_DIR } from "../config.js";
|
|
28
|
+
import { resolveConnections, ConnectionError } from "./connection-resolver.js";
|
|
29
|
+
import { PERSIST_HOOKS, UNPERSIST_HOOKS } from "./connection-apply.js";
|
|
30
|
+
import { categoryFromProviderCapability } from "./connection-resolver.js";
|
|
31
|
+
import * as capabilityRegistry from "./capability-registry.js";
|
|
32
|
+
import { withInstanceLock } from "../utils/instance-lock.js";
|
|
33
|
+
const APPLY_ORDER = ["default", "search", "browser", "llm", "mcp"];
|
|
34
|
+
export async function applyConnections(input) {
|
|
35
|
+
// Serialize against startApp/stopApp/restartApp on the same consumer
|
|
36
|
+
// instance. Without this, a concurrent stop can flip a bound provider
|
|
37
|
+
// to status:stopped between Validate (step 1) and Apply (step 3),
|
|
38
|
+
// leaving instance.json + side-effect files inconsistent.
|
|
39
|
+
return withInstanceLock(input.instance.id, () => applyConnectionsImpl(input));
|
|
40
|
+
}
|
|
41
|
+
async function applyConnectionsImpl(input) {
|
|
42
|
+
const { instance, spec, newConnections, saveInstanceJson, readInstanceJson, adapter } = input;
|
|
43
|
+
// ── Step 1: Validate ──────────────────────────────────────────────────
|
|
44
|
+
// Resolve the *new* connections in runtime mode so missing-required /
|
|
45
|
+
// ambiguous / invalid-binding all surface as ConnectionError before we
|
|
46
|
+
// touch any state.
|
|
47
|
+
const candidate = {
|
|
48
|
+
...instance,
|
|
49
|
+
connections: newConnections,
|
|
50
|
+
};
|
|
51
|
+
const { resolved } = resolveConnections(spec, candidate, "runtime");
|
|
52
|
+
// ── Step 1d: validateCapabilityEndpoint per binding (§12) ─────────────
|
|
53
|
+
// Catches registry tampering / drift before any persist hook fires:
|
|
54
|
+
// - Fresh re-resolve must match the registry entry (host/port/path)
|
|
55
|
+
// - Provider host must be on loopback or LAN
|
|
56
|
+
// - Protocol must be in the category whitelist
|
|
57
|
+
// Only applies to entries whose provider spec we can resolve; legacy
|
|
58
|
+
// instances without a synthesized spec degrade gracefully (skip).
|
|
59
|
+
await validateResolvedEndpoints(resolved);
|
|
60
|
+
// ── Step 2: Snapshot ──────────────────────────────────────────────────
|
|
61
|
+
// 覆盖 OpenClaw (V2+legacy)、Hermes (V2+legacy)、generic app-dir 三类
|
|
62
|
+
// consumer 的具体写入路径;详见 snapshotSideEffectFiles。
|
|
63
|
+
const snapshot = {
|
|
64
|
+
instanceJson: await readInstanceJson(instance.id),
|
|
65
|
+
files: snapshotSideEffectFiles(instance.id),
|
|
66
|
+
};
|
|
67
|
+
// ── Step 2b: Compute shrunk slots ─────────────────────────────────────
|
|
68
|
+
// PERSIST_HOOKS only sees slots that *currently* have a binding. When
|
|
69
|
+
// the user removes a binding (sets the slot to null or drops it from the
|
|
70
|
+
// map) we need to undo the durable state the prior persist hook wrote —
|
|
71
|
+
// otherwise unbinding LLM_ENDPOINT leaves x-jishushell.proxy.upstream
|
|
72
|
+
// pointing at the gone provider, etc. `shrunkSlots` enumerates exactly
|
|
73
|
+
// those: present-and-non-null in the on-disk connections, missing or
|
|
74
|
+
// null in `newConnections`.
|
|
75
|
+
const previousConnections = (snapshot.instanceJson?.connections ?? {});
|
|
76
|
+
const shrunkSlots = [];
|
|
77
|
+
for (const [slot, prior] of Object.entries(previousConnections)) {
|
|
78
|
+
if (prior == null)
|
|
79
|
+
continue; // already empty — nothing to undo
|
|
80
|
+
const next = newConnections?.[slot];
|
|
81
|
+
if (next != null)
|
|
82
|
+
continue; // still bound (or rebound) — persist hook handles it
|
|
83
|
+
const req = spec.requires?.find((r) => r.inject_as === slot);
|
|
84
|
+
const cap = req?.capability ?? "";
|
|
85
|
+
const fromToken = cap && (UNPERSIST_HOOKS[cap] ? cap : null);
|
|
86
|
+
const fromProvide = !fromToken ? categoryFromProviderCapability(cap) : null;
|
|
87
|
+
shrunkSlots.push({ slot, category: fromToken ?? fromProvide ?? "default" });
|
|
88
|
+
}
|
|
89
|
+
// ── Step 3: Apply hooks ───────────────────────────────────────────────
|
|
90
|
+
const ctx = makeApplyContext({ adapter, saveInstanceJson });
|
|
91
|
+
const appliedHooks = [];
|
|
92
|
+
try {
|
|
93
|
+
// 3a: unpersist removed slots BEFORE persisting new ones — if the user
|
|
94
|
+
// swapped one LLM provider for another, persist's overwrite already
|
|
95
|
+
// handles it; this loop only fires for slots that genuinely went
|
|
96
|
+
// away, so it's safe to run first. Failures here are logged and
|
|
97
|
+
// swallowed so a stale env var or stale upstream config doesn't
|
|
98
|
+
// block legitimate new bindings; the rollback step still recovers
|
|
99
|
+
// the full snapshot if a later persist throws.
|
|
100
|
+
for (const { slot, category } of shrunkSlots) {
|
|
101
|
+
const hook = UNPERSIST_HOOKS[category];
|
|
102
|
+
if (!hook)
|
|
103
|
+
continue;
|
|
104
|
+
try {
|
|
105
|
+
await hook(candidate, slot, ctx);
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
console.warn(`[connection-transactor] unpersist ${category}/${slot} for ${instance.id} failed: ${e?.message ?? e}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const category of APPLY_ORDER) {
|
|
112
|
+
const hook = PERSIST_HOOKS[category];
|
|
113
|
+
if (!hook)
|
|
114
|
+
continue;
|
|
115
|
+
for (const binding of resolved) {
|
|
116
|
+
if (binding.category !== category)
|
|
117
|
+
continue;
|
|
118
|
+
await hook(candidate, binding, ctx);
|
|
119
|
+
appliedHooks.push(binding);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
await rollback(input, snapshot);
|
|
125
|
+
if (e instanceof ConnectionError)
|
|
126
|
+
throw e;
|
|
127
|
+
throw new ConnectionError("CONNECTION_APPLY_FAILED", 500, `Connection apply failed: ${e?.message ?? e}`, { applied: appliedHooks.map((b) => b.slot) });
|
|
128
|
+
}
|
|
129
|
+
// ── Step 4: Persist connections ───────────────────────────────────────
|
|
130
|
+
// Read-modify-write so we don't clobber `connections-env` that step 3
|
|
131
|
+
// hooks may have written for generic apps without an adapter.
|
|
132
|
+
try {
|
|
133
|
+
await saveInstanceJson(instance.id, (cur) => ({
|
|
134
|
+
...cur,
|
|
135
|
+
connections: newConnections,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
await rollback(input, snapshot);
|
|
140
|
+
throw new ConnectionError("CONNECTION_PERSIST_FAILED", 500, `Failed to persist connections: ${e?.message ?? e}`);
|
|
141
|
+
}
|
|
142
|
+
return { resolved };
|
|
143
|
+
}
|
|
144
|
+
function makeApplyContext(opts) {
|
|
145
|
+
return {
|
|
146
|
+
registry: capabilityRegistry,
|
|
147
|
+
adapter: opts.adapter,
|
|
148
|
+
async writeConnectionEnv(instance, env) {
|
|
149
|
+
if (Object.keys(env).length === 0)
|
|
150
|
+
return;
|
|
151
|
+
if (opts.adapter && typeof opts.adapter.applyConnectionEnv === "function") {
|
|
152
|
+
await opts.adapter.applyConnectionEnv(instance.id, env);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Generic container app — persist into instance.json["connections-env"].
|
|
156
|
+
await opts.saveInstanceJson(instance.id, (cur) => ({
|
|
157
|
+
...cur,
|
|
158
|
+
"connections-env": {
|
|
159
|
+
...(cur["connections-env"] ?? {}),
|
|
160
|
+
...env,
|
|
161
|
+
},
|
|
162
|
+
}));
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Test-only export so unit tests can exercise the snapshot/restore path
|
|
168
|
+
* without spinning up the full transactor. Production code should not
|
|
169
|
+
* import these names directly.
|
|
170
|
+
*/
|
|
171
|
+
export const __testing__ = {
|
|
172
|
+
snapshotSideEffectFiles: (instanceId) => snapshotSideEffectFiles(instanceId),
|
|
173
|
+
applyFileSnapshots: (snapshots) => applyFileSnapshots(snapshots),
|
|
174
|
+
validateResolvedEndpoints: (resolved) => validateResolvedEndpoints(resolved),
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Walk every resolved binding and validate each provider entry's endpoint
|
|
178
|
+
* via `validateCapabilityEndpoint` (§12). Looks the provider spec up
|
|
179
|
+
* either through the app-manager (app-installed providers) or through the
|
|
180
|
+
* legacy synthesizer (instance-backed providers). When neither lookup
|
|
181
|
+
* yields a spec, the entry is left un-validated rather than rejected —
|
|
182
|
+
* the design's protection target is registry tampering after legitimate
|
|
183
|
+
* registration, and there's no spec to compare against in the missing case.
|
|
184
|
+
*/
|
|
185
|
+
async function validateResolvedEndpoints(resolved) {
|
|
186
|
+
if (resolved.length === 0)
|
|
187
|
+
return;
|
|
188
|
+
const { validateCapabilityEndpoint, findProviderProvide } = await import("./capability-endpoint-validator.js");
|
|
189
|
+
const { getApp } = await import("./app/app-manager.js");
|
|
190
|
+
const { loadCapabilitySpecForLegacyInstance } = await import("./runtime/migrations.js");
|
|
191
|
+
const legacyInstanceManager = await import("./instance-manager.js");
|
|
192
|
+
for (const binding of resolved) {
|
|
193
|
+
const category = binding.category;
|
|
194
|
+
for (const entry of binding.entries) {
|
|
195
|
+
// Look up the provider spec. Two paths:
|
|
196
|
+
// 1. App-installed provider — `getApp(entry.instanceId).spec`
|
|
197
|
+
// 2. Legacy instance-backed provider — synthesize from agent template
|
|
198
|
+
let providerSpec = null;
|
|
199
|
+
let isLegacySynthetic = false;
|
|
200
|
+
const appRecord = getApp(entry.instanceId);
|
|
201
|
+
if (appRecord) {
|
|
202
|
+
providerSpec = appRecord.spec;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const meta = legacyInstanceManager.getInstance(entry.instanceId);
|
|
206
|
+
providerSpec = loadCapabilitySpecForLegacyInstance(meta);
|
|
207
|
+
isLegacySynthetic = !!providerSpec;
|
|
208
|
+
}
|
|
209
|
+
if (!providerSpec) {
|
|
210
|
+
// No spec we can validate against AND no live instance to synthesize
|
|
211
|
+
// one from. The registry entry is orphaned — treat as a hard reject
|
|
212
|
+
// so a tampered registry can't sneak past the validator simply by
|
|
213
|
+
// pointing at an instanceId that no longer exists.
|
|
214
|
+
const { ConnectionError } = await import("./connection-resolver.js");
|
|
215
|
+
throw new ConnectionError("INVALID_CAPABILITY_ENDPOINT", 400, `Provider '${entry.instanceId}' has no installed app or live instance — registry entry is orphaned`, {
|
|
216
|
+
providerInstanceId: entry.instanceId,
|
|
217
|
+
capability: entry.capability,
|
|
218
|
+
reason: "provider-spec-missing",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const provide = findProviderProvide(providerSpec, entry.capability);
|
|
222
|
+
if (!provide) {
|
|
223
|
+
if (isLegacySynthetic) {
|
|
224
|
+
// Legacy synthetic specs are minimal (id/name/requires/provides
|
|
225
|
+
// only). If the agent template hasn't declared this capability
|
|
226
|
+
// there's nothing to validate against — degrade rather than
|
|
227
|
+
// reject so we don't break working bindings on older instances.
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
// App-installed provider whose spec doesn't declare this capability.
|
|
231
|
+
// Either the registry was tampered to forge a capability the spec
|
|
232
|
+
// doesn't expose, or the spec drifted out of sync. Either way the
|
|
233
|
+
// safe move is to reject — apply hooks would otherwise consume a
|
|
234
|
+
// registry-supplied host/port/protocol with no spec confirmation.
|
|
235
|
+
const { ConnectionError } = await import("./connection-resolver.js");
|
|
236
|
+
throw new ConnectionError("INVALID_CAPABILITY_ENDPOINT", 400, `Provider '${entry.instanceId}' app spec does not declare capability '${entry.capability}'`, {
|
|
237
|
+
providerInstanceId: entry.instanceId,
|
|
238
|
+
capability: entry.capability,
|
|
239
|
+
reason: "capability-not-in-spec",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
validateCapabilityEndpoint(providerSpec, entry.instanceId, provide, entry, category);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Capture raw bytes of files that PERSIST_HOOKS may write through. Used to
|
|
248
|
+
* roll back partial mutations when a later hook throws. Files that did not
|
|
249
|
+
* exist at snapshot time are recorded with `bytes: null` and unlinked on
|
|
250
|
+
* rollback so we don't leave behind half-written state.
|
|
251
|
+
*
|
|
252
|
+
* 覆盖三类 consumer 的所有已知写入路径:
|
|
253
|
+
* - OpenClaw:provider.env / openclaw.json / mcporter.json(V2 + legacy)
|
|
254
|
+
* - Hermes:agent-home/config.yaml + agent-home/.env(V2 + legacy)
|
|
255
|
+
* - Generic app-dir:apps/<id>/instance.json
|
|
256
|
+
*
|
|
257
|
+
* 路径前缀来自 INSTANCES_DIR / APPS_DIR(均读 JISHUSHELL_HOME 环境变量),
|
|
258
|
+
* 不存在的路径记为 bytes:null,回滚时做 unlink 而非写入。
|
|
259
|
+
*/
|
|
260
|
+
function snapshotSideEffectFiles(instanceId) {
|
|
261
|
+
const legacyHome = join(INSTANCES_DIR, instanceId);
|
|
262
|
+
const v2Home = join(APPS_DIR, instanceId);
|
|
263
|
+
const candidates = [
|
|
264
|
+
// ── Per-instance secret bag (LLM proxy-upstream) ──
|
|
265
|
+
join(legacyHome, "provider.env"),
|
|
266
|
+
// ── OpenClaw-native config (V2 + legacy layouts) ──
|
|
267
|
+
join(legacyHome, "openclaw-home", ".openclaw", "openclaw.json"),
|
|
268
|
+
join(v2Home, "openclaw-home", ".openclaw", "openclaw.json"),
|
|
269
|
+
// ── MCP server registry consumed by the OpenClaw runtime ──
|
|
270
|
+
join(legacyHome, "openclaw-home", ".openclaw", "workspace", "config", "mcporter.json"),
|
|
271
|
+
join(v2Home, "openclaw-home", ".openclaw", "workspace", "config", "mcporter.json"),
|
|
272
|
+
// ── Hermes adapter writes (V2 layout): config.yaml + .env via
|
|
273
|
+
// applyConnectionEnv. Without these in the snapshot, a partial MCP
|
|
274
|
+
// apply for the search slot leaves a half-written config.yaml when
|
|
275
|
+
// a later LLM hook fails. Path matches `resolveHermesPaths`.
|
|
276
|
+
join(v2Home, "agent-home", "config.yaml"),
|
|
277
|
+
join(v2Home, "agent-home", ".env"),
|
|
278
|
+
// ── Hermes legacy layout (pre-V2 instances) ──
|
|
279
|
+
join(legacyHome, "agent-home", "config.yaml"),
|
|
280
|
+
join(legacyHome, "agent-home", ".env"),
|
|
281
|
+
// ── Generic app-dir consumer (e.g. OpenWebUI) — instance.json
|
|
282
|
+
// holds the persisted `connections-env` map written by the
|
|
283
|
+
// openai-env hook for non-adapter apps. Already covered by
|
|
284
|
+
// `snapshot.instanceJson`, but listed here as an extra safety net
|
|
285
|
+
// so a partial direct write at apps/<id>/instance.json (outside
|
|
286
|
+
// the saveInstanceJson code path) still rolls back. ──
|
|
287
|
+
join(v2Home, "instance.json"),
|
|
288
|
+
];
|
|
289
|
+
const out = [];
|
|
290
|
+
for (const path of candidates) {
|
|
291
|
+
try {
|
|
292
|
+
out.push({ path, bytes: existsSync(path) ? readFileSync(path) : null });
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
if (e.code === "ENOENT") {
|
|
296
|
+
// File disappeared between existsSync and readFileSync (TOCTOU) — treat as absent.
|
|
297
|
+
out.push({ path, bytes: null });
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
console.warn(`[connection-transactor] snapshot read failed for ${path}: ${e?.message ?? e}`);
|
|
301
|
+
out.push({ path, bytes: null, readError: true });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Restore each file to its snapshotted bytes. Files whose snapshot recorded
|
|
309
|
+
* `bytes: null` (didn't exist pre-apply) are unlinked. Errors are logged
|
|
310
|
+
* per-file so a single failed restore doesn't block the rest.
|
|
311
|
+
*/
|
|
312
|
+
function applyFileSnapshots(snapshots) {
|
|
313
|
+
for (const file of snapshots) {
|
|
314
|
+
if (file.readError) {
|
|
315
|
+
console.warn(`[connection-transactor] skipping rollback for ${file.path} (snapshot read had failed)`);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
if (file.bytes === null) {
|
|
320
|
+
if (existsSync(file.path))
|
|
321
|
+
unlinkSync(file.path);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
try {
|
|
325
|
+
mkdirSync(dirname(file.path), { recursive: true });
|
|
326
|
+
}
|
|
327
|
+
catch { /* dir likely exists */ }
|
|
328
|
+
writeFileSync(file.path, file.bytes);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
console.error(`[connection-transactor] rollback file restore failed for ${file.path}: ${e?.message ?? e}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async function rollback(input, snapshot) {
|
|
337
|
+
// Restore side-effect files first so their state is consistent before the
|
|
338
|
+
// instance.json `connections` map is rewound. Failures are logged but not
|
|
339
|
+
// propagated — at this point the caller has already seen a CONNECTION_*
|
|
340
|
+
// error and we want to give every later restore step a chance to run.
|
|
341
|
+
applyFileSnapshots(snapshot.files);
|
|
342
|
+
try {
|
|
343
|
+
// Single read-modify-write that overlays the snapshot on the current
|
|
344
|
+
// instance.json — atomic rollback for the single-file state.
|
|
345
|
+
await input.saveInstanceJson(input.instance.id, () => snapshot.instanceJson);
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
console.error(`[connection-transactor] rollback failed: ${e?.message ?? e}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=connection-transactor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-transactor.js","sourceRoot":"","sources":["../../src/services/connection-transactor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface ExternalMount {
|
|
2
|
+
alias: string;
|
|
3
|
+
host_path: string;
|
|
4
|
+
mode: "ro" | "rw";
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface MountValidationError {
|
|
8
|
+
field: string;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MountValidationResult {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
errors: MountValidationError[];
|
|
14
|
+
warnings: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validate a single mount candidate. Returns {ok, errors, warnings};
|
|
18
|
+
* warnings flag sensitive paths (e.g. ~/.ssh) but do NOT block the
|
|
19
|
+
* caller — that call is the user's policy decision.
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateMount(m: Partial<ExternalMount>, existing?: ExternalMount[]): MountValidationResult;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a files/-relative path that MIGHT start with an external
|
|
24
|
+
* mount alias to its true host filesystem path. Returns null when the
|
|
25
|
+
* path doesn't begin with a registered alias — caller falls back to
|
|
26
|
+
* `FILES_ROOT/<rel>`.
|
|
27
|
+
*
|
|
28
|
+
* Symlink defense and path-safety MUST run on the returned absolute
|
|
29
|
+
* path against `mount.host_path` (not FILES_ROOT) — caller's job.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveAcrossMounts(rel: string, mounts: ExternalMount[]): {
|
|
32
|
+
abs: string;
|
|
33
|
+
mount: ExternalMount;
|
|
34
|
+
remainder: string;
|
|
35
|
+
} | null;
|
|
36
|
+
/**
|
|
37
|
+
* Returns true if the given files/-relative path lands inside a
|
|
38
|
+
* read-only mount (any operation that mutates must reject early).
|
|
39
|
+
*/
|
|
40
|
+
export declare function isReadOnlyTarget(rel: string, mounts: ExternalMount[]): boolean;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External mounts — let any host directory appear as a virtual subtree
|
|
3
|
+
* inside ~/.jishushell/files/ (M3 W4 PR-4).
|
|
4
|
+
*
|
|
5
|
+
* Design choice: virtualize at the FilesManager layer rather than
|
|
6
|
+
* planting a symlink under FILES_ROOT.
|
|
7
|
+
*
|
|
8
|
+
* 1. The W1 symlink-defense rejects ALL symlinks anywhere in the
|
|
9
|
+
* path chain. We don't want to whitelist symlinks generally;
|
|
10
|
+
* the alias check must be code-explicit.
|
|
11
|
+
* 2. Symlinks behave inconsistently across docker bind mounts,
|
|
12
|
+
* WebDAV clients, and macOS APFS firmlinks. A virtual layer
|
|
13
|
+
* sidesteps the entire mess.
|
|
14
|
+
* 3. Listing the FILES_ROOT directory cannot include a symlink
|
|
15
|
+
* that points outside (the listing helper hides them); a
|
|
16
|
+
* virtual entry is just a synthesized DirEntry.
|
|
17
|
+
*
|
|
18
|
+
* The user-visible model:
|
|
19
|
+
* files/ ← real POSIX root
|
|
20
|
+
* ├── notes/ ← real subdirectory
|
|
21
|
+
* ├── photos/ ← real subdirectory
|
|
22
|
+
* └── Documents/ ← VIRTUAL — host_path = /Users/me/Documents
|
|
23
|
+
*
|
|
24
|
+
* From every layer's perspective (panel UI, /api/files/*, WebDAV,
|
|
25
|
+
* Agent drive shim), Documents/ behaves like any other directory.
|
|
26
|
+
* Mode "ro" is enforced uniformly.
|
|
27
|
+
*
|
|
28
|
+
* Sensitive paths flagged by sanity-check (caller can override):
|
|
29
|
+
* /etc, /proc, /sys, /dev, /var/log, ~/.ssh, ~/.aws, ~/.gnupg,
|
|
30
|
+
* anywhere inside JISHUSHELL_HOME (would create recursion)
|
|
31
|
+
*/
|
|
32
|
+
import * as fs from "node:fs";
|
|
33
|
+
import * as path from "node:path";
|
|
34
|
+
import { homedir } from "node:os";
|
|
35
|
+
import { JISHUSHELL_HOME, FILES_ROOT } from "../config.js";
|
|
36
|
+
const ALIAS_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,63}$/;
|
|
37
|
+
const DENY_PREFIXES = [
|
|
38
|
+
"/etc",
|
|
39
|
+
"/proc",
|
|
40
|
+
"/sys",
|
|
41
|
+
"/dev",
|
|
42
|
+
"/run",
|
|
43
|
+
"/boot",
|
|
44
|
+
"/var/log",
|
|
45
|
+
"/var/lib",
|
|
46
|
+
];
|
|
47
|
+
const SUSPICIOUS_HIDDEN = [".ssh", ".aws", ".gnupg", ".config/gh"];
|
|
48
|
+
/**
|
|
49
|
+
* Reserved aliases that cannot be used because they collide with
|
|
50
|
+
* panel-internal directories or W1.5 / W2 conventions.
|
|
51
|
+
*/
|
|
52
|
+
const RESERVED_ALIASES = new Set([
|
|
53
|
+
".trash",
|
|
54
|
+
"agent-data",
|
|
55
|
+
"legacy",
|
|
56
|
+
"skills",
|
|
57
|
+
"memory",
|
|
58
|
+
".knowledge",
|
|
59
|
+
]);
|
|
60
|
+
/**
|
|
61
|
+
* Validate a single mount candidate. Returns {ok, errors, warnings};
|
|
62
|
+
* warnings flag sensitive paths (e.g. ~/.ssh) but do NOT block the
|
|
63
|
+
* caller — that call is the user's policy decision.
|
|
64
|
+
*/
|
|
65
|
+
export function validateMount(m, existing = []) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
const warnings = [];
|
|
68
|
+
// alias
|
|
69
|
+
if (!m.alias || typeof m.alias !== "string") {
|
|
70
|
+
errors.push({ field: "alias", message: "required string" });
|
|
71
|
+
}
|
|
72
|
+
else if (!ALIAS_RE.test(m.alias)) {
|
|
73
|
+
errors.push({
|
|
74
|
+
field: "alias",
|
|
75
|
+
message: "must match [a-zA-Z0-9_.-]+ (no slashes), <= 64 chars",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else if (RESERVED_ALIASES.has(m.alias)) {
|
|
79
|
+
errors.push({ field: "alias", message: `"${m.alias}" is a reserved alias` });
|
|
80
|
+
}
|
|
81
|
+
else if (existing.some((e) => e.alias === m.alias)) {
|
|
82
|
+
errors.push({ field: "alias", message: `"${m.alias}" already in use` });
|
|
83
|
+
}
|
|
84
|
+
// mode
|
|
85
|
+
if (m.mode !== "ro" && m.mode !== "rw") {
|
|
86
|
+
errors.push({ field: "mode", message: 'must be "ro" or "rw"' });
|
|
87
|
+
}
|
|
88
|
+
// host_path
|
|
89
|
+
if (!m.host_path || typeof m.host_path !== "string") {
|
|
90
|
+
errors.push({ field: "host_path", message: "required absolute path" });
|
|
91
|
+
}
|
|
92
|
+
else if (!path.isAbsolute(m.host_path)) {
|
|
93
|
+
errors.push({ field: "host_path", message: "must be absolute" });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Resolve any ~ expansion the caller forgot
|
|
97
|
+
const resolved = m.host_path.startsWith("~")
|
|
98
|
+
? path.join(homedir(), m.host_path.slice(1))
|
|
99
|
+
: path.resolve(m.host_path);
|
|
100
|
+
if (!fs.existsSync(resolved)) {
|
|
101
|
+
errors.push({
|
|
102
|
+
field: "host_path",
|
|
103
|
+
message: `path does not exist: ${resolved}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
try {
|
|
108
|
+
const stat = fs.lstatSync(resolved);
|
|
109
|
+
if (stat.isSymbolicLink()) {
|
|
110
|
+
errors.push({
|
|
111
|
+
field: "host_path",
|
|
112
|
+
message: "must be a real directory, not a symlink",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else if (!stat.isDirectory()) {
|
|
116
|
+
errors.push({
|
|
117
|
+
field: "host_path",
|
|
118
|
+
message: "must be a directory",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
errors.push({
|
|
124
|
+
field: "host_path",
|
|
125
|
+
message: `cannot stat: ${e.message}`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Recursion guard: must not be inside FILES_ROOT or JISHUSHELL_HOME
|
|
129
|
+
if (resolved === FILES_ROOT ||
|
|
130
|
+
resolved.startsWith(FILES_ROOT + path.sep) ||
|
|
131
|
+
resolved === JISHUSHELL_HOME ||
|
|
132
|
+
resolved.startsWith(JISHUSHELL_HOME + path.sep)) {
|
|
133
|
+
errors.push({
|
|
134
|
+
field: "host_path",
|
|
135
|
+
message: "cannot mount a path inside ~/.jishushell — would cause recursion",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Sensitive prefix warnings (do not block)
|
|
139
|
+
for (const prefix of DENY_PREFIXES) {
|
|
140
|
+
if (resolved === prefix || resolved.startsWith(prefix + "/")) {
|
|
141
|
+
warnings.push(`host_path is under ${prefix} — system directory, mode=ro is strongly recommended`);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const hidden of SUSPICIOUS_HIDDEN) {
|
|
146
|
+
if (resolved.includes(`/${hidden}`) || resolved.includes(`\\${hidden}`)) {
|
|
147
|
+
warnings.push(`host_path contains "${hidden}" — likely holds credentials, mode=ro recommended`);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Resolve a files/-relative path that MIGHT start with an external
|
|
157
|
+
* mount alias to its true host filesystem path. Returns null when the
|
|
158
|
+
* path doesn't begin with a registered alias — caller falls back to
|
|
159
|
+
* `FILES_ROOT/<rel>`.
|
|
160
|
+
*
|
|
161
|
+
* Symlink defense and path-safety MUST run on the returned absolute
|
|
162
|
+
* path against `mount.host_path` (not FILES_ROOT) — caller's job.
|
|
163
|
+
*/
|
|
164
|
+
export function resolveAcrossMounts(rel, mounts) {
|
|
165
|
+
if (!rel)
|
|
166
|
+
return null;
|
|
167
|
+
const slash = rel.indexOf("/");
|
|
168
|
+
const first = slash < 0 ? rel : rel.slice(0, slash);
|
|
169
|
+
const remainder = slash < 0 ? "" : rel.slice(slash + 1);
|
|
170
|
+
const m = mounts.find((x) => x.alias === first);
|
|
171
|
+
if (!m)
|
|
172
|
+
return null;
|
|
173
|
+
const expanded = m.host_path.startsWith("~")
|
|
174
|
+
? path.join(homedir(), m.host_path.slice(1))
|
|
175
|
+
: path.resolve(m.host_path);
|
|
176
|
+
const abs = remainder ? path.join(expanded, remainder) : expanded;
|
|
177
|
+
return { abs, mount: m, remainder };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Returns true if the given files/-relative path lands inside a
|
|
181
|
+
* read-only mount (any operation that mutates must reject early).
|
|
182
|
+
*/
|
|
183
|
+
export function isReadOnlyTarget(rel, mounts) {
|
|
184
|
+
const m = resolveAcrossMounts(rel, mounts);
|
|
185
|
+
return !!(m && m.mount.mode === "ro");
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=external-mounts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-mounts.js","sourceRoot":"","sources":["../../src/services/external-mounts.ts"],"names":[],"mappings":"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"}
|