@hope_phenom/dsh-plugin-remote-use 0.0.0-stage → 0.2.24

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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +207 -2
  3. package/README.zh.md +195 -0
  4. package/assets/icon.svg +47 -0
  5. package/cordis.patch.yml +41 -0
  6. package/lib/bridge/acp-bridge.js +905 -0
  7. package/lib/bridge/peer-connection.js +730 -0
  8. package/lib/client.js +2842 -0
  9. package/lib/core/acp-model-overlay.js +121 -0
  10. package/lib/core/approval-queue.js +216 -0
  11. package/lib/core/audit-log.js +322 -0
  12. package/lib/core/desktop-notify.js +173 -0
  13. package/lib/core/environment-prompt.js +76 -0
  14. package/lib/core/relay-supervisor.js +278 -0
  15. package/lib/core/review-engine.js +679 -0
  16. package/lib/core/sandbox-diagnosis.js +258 -0
  17. package/lib/core/sandbox-probe.js +310 -0
  18. package/lib/core/self-acp-spec.js +117 -0
  19. package/lib/core/selfcheck-runner.js +150 -0
  20. package/lib/core/settings-store.js +226 -0
  21. package/lib/core/shot-gc.js +334 -0
  22. package/lib/core/task-cwd.js +50 -0
  23. package/lib/core/task-memory.js +163 -0
  24. package/lib/core/task-registry.js +238 -0
  25. package/lib/core/toolcall-cache.js +206 -0
  26. package/lib/core/transfer-queue.js +58 -0
  27. package/lib/core/transfer.js +1144 -0
  28. package/lib/core/worker-arming.js +334 -0
  29. package/lib/index.js +2207 -0
  30. package/lib/provider/out-of-process-helpers.js +76 -0
  31. package/lib/provider/remote-provider.js +261 -0
  32. package/lib/tools/mediator-shim.mjs +180 -0
  33. package/lib/tools/prepare-sandbox-mediator.mjs +529 -0
  34. package/lib/tools/remote-tools.js +680 -0
  35. package/lib/tools/selfcheck-fix-prompt.md +206 -0
  36. package/locale/en.json +6 -0
  37. package/locale/zh.json +6 -0
  38. package/package.json +72 -4
  39. package/slave/acp-profile-approval.patch.yml +66 -0
  40. package/slave/file-transfer.mjs +552 -0
  41. package/slave/integrity-probe.ps1 +99 -0
  42. package/slave/make-launch-spec.mjs +184 -0
  43. package/slave/net-address.mjs +138 -0
  44. package/slave/node_modules/.package-lock.json +26 -0
  45. package/slave/node_modules/ws/LICENSE +20 -0
  46. package/slave/node_modules/ws/README.md +548 -0
  47. package/slave/node_modules/ws/browser.js +8 -0
  48. package/slave/node_modules/ws/index.js +22 -0
  49. package/slave/node_modules/ws/lib/buffer-util.js +131 -0
  50. package/slave/node_modules/ws/lib/constants.js +19 -0
  51. package/slave/node_modules/ws/lib/event-target.js +292 -0
  52. package/slave/node_modules/ws/lib/extension.js +203 -0
  53. package/slave/node_modules/ws/lib/limiter.js +55 -0
  54. package/slave/node_modules/ws/lib/permessage-deflate.js +530 -0
  55. package/slave/node_modules/ws/lib/receiver.js +743 -0
  56. package/slave/node_modules/ws/lib/sender.js +607 -0
  57. package/slave/node_modules/ws/lib/stream.js +161 -0
  58. package/slave/node_modules/ws/lib/subprotocol.js +62 -0
  59. package/slave/node_modules/ws/lib/validation.js +152 -0
  60. package/slave/node_modules/ws/lib/websocket-server.js +562 -0
  61. package/slave/node_modules/ws/lib/websocket.js +1421 -0
  62. package/slave/node_modules/ws/package.json +74 -0
  63. package/slave/node_modules/ws/wrapper.mjs +21 -0
  64. package/slave/pairing.mjs +175 -0
  65. package/slave/permission-pairing.mjs +240 -0
  66. package/slave/prune-shots.mjs +170 -0
  67. package/slave/relay.mjs +1351 -0
  68. package/slave/repair-workspace-integrity.ps1 +75 -0
  69. package/slave/screenshot.ps1 +114 -0
  70. package/slave/selfcheck.ps1 +350 -0
  71. package/slave/stage-app.ps1 +116 -0
  72. package/slave/workspace-integrity.mjs +157 -0
@@ -0,0 +1,157 @@
1
+ // workspace-integrity.mjs -- clear the stale Low mandatory label a full-access workspace inherits.
2
+ //
3
+ // WHY THIS IS NEEDED, measured on a real Windows worker (2026-10-19):
4
+ //
5
+ // DSH's `workspace-write` sandbox does not only lower the child token to Low integrity
6
+ // (S-1-16-4096). It also writes an inheritable Low mandatory label onto the workspace directory and
7
+ // its temp root. Anything copied into that tree inherits the label -- and Windows launches an image
8
+ // carrying a Low label at Low integrity even when the parent token is High. The result is a GUI
9
+ // application that cannot write `%LOCALAPPDATA%` (Medium) while the agent shell around it can:
10
+ //
11
+ // pwsh (agent shell) -> High
12
+ // Start-Process C:\Windows\System32\whoami.exe -> High
13
+ // copy of whoami.exe under the workspace -> Low
14
+ //
15
+ // The label outlives the session that added it, so switching the worker to `danger-full-access`
16
+ // does not undo it: the stale label keeps downgrading every app in the workspace. Under full access
17
+ // the confinement has been deliberately turned off, so the Low label is not protecting anything --
18
+ // it is a leftover, and it actively breaks the GUI workflow that full access exists to enable.
19
+ //
20
+ // THE REPAIR IS DELIBERATELY NARROW:
21
+ // * only on Windows;
22
+ // * only when the effective mode is `danger-full-access` (a workspace-write worker MUST keep its
23
+ // Low label -- that label is what confines it);
24
+ // * only for an explicit task directory (`--task-dir`), or when `--repair-integrity` asks for the
25
+ // relay's inherited cwd;
26
+ // * never for a drive root, the Windows/Program Files/ProgramData trees, or the user profile root.
27
+ //
28
+ // `icacls /T` is recursive because changing only the top directory leaves nested directories and
29
+ // the executables already inside them at Low (measured: a parent relabel updates a direct child but
30
+ // not a file two levels down). It is idempotent and only runs once per relay start.
31
+
32
+ import { spawnSync } from 'node:child_process'
33
+ import { existsSync, statSync } from 'node:fs'
34
+ import { join, parse, resolve } from 'node:path'
35
+
36
+ /** Every environment variable that names a directory this module must never relabel. */
37
+ const PROTECTED_ROOTS = ['SystemRoot', 'windir', 'ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432', 'ProgramData']
38
+
39
+ /** Case-insensitive comparison form for a Windows path, without a trailing separator. */
40
+ export function normalizePath(path) {
41
+ if (typeof path !== 'string' || path.trim() === '') return ''
42
+ return resolve(path).replace(/[\\/]+$/u, '').toLowerCase()
43
+ }
44
+
45
+ /**
46
+ * Whether `path` is a system location whose mandatory label must not be touched.
47
+ *
48
+ * A worker whose relay inherited `C:\Windows\System32` as its cwd (a scheduled task with no
49
+ * explicit working directory) would otherwise have its whole System32 tree relabeled on startup.
50
+ * That is far worse than the problem being fixed, so the guard errs toward refusing.
51
+ *
52
+ * @param {string} path
53
+ * @param {Record<string, string | undefined>} [env]
54
+ * @returns {boolean}
55
+ */
56
+ export function isProtectedWorkspace(path, env = process.env) {
57
+ const target = normalizePath(path)
58
+ if (target === '') return true
59
+ // A drive root ("c:\\") or a UNC share root is not a workspace.
60
+ if (target === normalizePath(parse(resolve(path)).root)) return true
61
+ for (const name of PROTECTED_ROOTS) {
62
+ const base = env[name]
63
+ if (typeof base !== 'string' || base.trim() === '') continue
64
+ const root = normalizePath(base)
65
+ if (target === root || target.startsWith(`${root}\\`)) return true
66
+ }
67
+ // The user profile root itself is not a workspace either; `C:\Users\<name>\work` is fine.
68
+ const profile = env.USERPROFILE
69
+ if (typeof profile === 'string' && profile.trim() !== '' && target === normalizePath(profile)) return true
70
+ return false
71
+ }
72
+
73
+ /**
74
+ * Relabel one workspace tree from Low to Medium, when full access makes that the correct state.
75
+ *
76
+ * @param {object} [deps]
77
+ * @param {string} [deps.mode] the EFFECTIVE session mode the relay will spawn
78
+ * @param {string} [deps.dir] the task directory
79
+ * @param {boolean} [deps.declared] true when `--task-dir` named it explicitly
80
+ * @param {boolean} [deps.force] `--repair-integrity`: allow the inherited cwd too
81
+ * @param {number} [deps.timeoutMs] bound on the recursive pass
82
+ * @param {(file: string, args: string[], opts: object) => object} [deps.spawn] injected for tests
83
+ * @returns {{ applied: boolean, skipped: boolean, reason: string, error: string,
84
+ * dir: string, durationMs: number, timedOut: boolean }}
85
+ */
86
+ export function repairWorkspaceIntegrity(deps = {}) {
87
+ const started = Date.now()
88
+ const result = (fields) => ({
89
+ applied: false,
90
+ skipped: true,
91
+ reason: '',
92
+ error: '',
93
+ dir: '',
94
+ durationMs: Date.now() - started,
95
+ timedOut: false,
96
+ ...fields,
97
+ })
98
+
99
+ if (process.platform !== 'win32') return result({ reason: 'mandatory labels are a Windows concept' })
100
+
101
+ const mode = String(deps.mode ?? '')
102
+ if (mode !== 'danger-full-access') {
103
+ return result({ reason: `effective mode is ${mode === '' ? 'not set' : mode}; a confined workspace must keep its Low label` })
104
+ }
105
+
106
+ const dir = typeof deps.dir === 'string' ? deps.dir.trim() : ''
107
+ if (dir === '') return result({ reason: 'no task directory to repair' })
108
+ if (deps.declared !== true && deps.force !== true) {
109
+ return result({ reason: 'the task directory was inherited, not named with --task-dir (pass --repair-integrity to allow it)' })
110
+ }
111
+ if (!existsSync(dir)) return result({ reason: `task directory does not exist: ${dir}` })
112
+ try {
113
+ if (!statSync(dir).isDirectory()) return result({ reason: `task directory is not a directory: ${dir}` })
114
+ } catch (error) {
115
+ return result({ reason: `cannot inspect ${dir}: ${error instanceof Error ? error.message : String(error)}` })
116
+ }
117
+ if (isProtectedWorkspace(dir)) {
118
+ return result({ reason: `refusing to relabel a protected system location: ${dir}` })
119
+ }
120
+
121
+ const icacls = join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'icacls.exe')
122
+ const executable = existsSync(icacls) ? icacls : 'icacls.exe'
123
+ const args = [dir, '/setintegritylevel', '(OI)(CI)Medium', '/T', '/C', '/Q']
124
+ const run = (deps.spawn ?? spawnSync)(executable, args, {
125
+ encoding: 'utf8',
126
+ timeout: deps.timeoutMs ?? 60_000,
127
+ windowsHide: true,
128
+ })
129
+
130
+ const timedOut = run?.error?.code === 'ETIMEDOUT'
131
+ if (run?.error !== undefined && run.error !== null && !timedOut) {
132
+ return result({
133
+ dir,
134
+ reason: 'icacls could not run',
135
+ error: run.error instanceof Error ? run.error.message : String(run.error),
136
+ timedOut,
137
+ })
138
+ }
139
+ if (!timedOut && run?.status !== 0) {
140
+ return result({
141
+ dir,
142
+ reason: 'icacls refused',
143
+ error: String(run?.stderr ?? '').trim().slice(0, 400) || `exit ${String(run?.status)}`,
144
+ timedOut,
145
+ })
146
+ }
147
+
148
+ return result({
149
+ applied: true,
150
+ skipped: false,
151
+ dir,
152
+ timedOut,
153
+ reason: timedOut
154
+ ? 'the recursive relabel hit its time limit; part of the tree may still be Low'
155
+ : 'workspace relabeled to Medium because this session runs at danger-full-access',
156
+ })
157
+ }