@rozek/nanoclaw 1.2.17

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 (305) hide show
  1. package/.claude/settings.json +1 -0
  2. package/.claude/skills/add-compact/SKILL.md +135 -0
  3. package/.claude/skills/add-discord/SKILL.md +203 -0
  4. package/.claude/skills/add-gmail/SKILL.md +220 -0
  5. package/.claude/skills/add-image-vision/SKILL.md +94 -0
  6. package/.claude/skills/add-ollama-tool/SKILL.md +153 -0
  7. package/.claude/skills/add-parallel/SKILL.md +290 -0
  8. package/.claude/skills/add-pdf-reader/SKILL.md +104 -0
  9. package/.claude/skills/add-reactions/SKILL.md +117 -0
  10. package/.claude/skills/add-slack/SKILL.md +207 -0
  11. package/.claude/skills/add-telegram/SKILL.md +222 -0
  12. package/.claude/skills/add-telegram-swarm/SKILL.md +384 -0
  13. package/.claude/skills/add-voice-transcription/SKILL.md +148 -0
  14. package/.claude/skills/add-whatsapp/SKILL.md +372 -0
  15. package/.claude/skills/convert-to-apple-container/SKILL.md +175 -0
  16. package/.claude/skills/customize/SKILL.md +110 -0
  17. package/.claude/skills/debug/SKILL.md +349 -0
  18. package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
  19. package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
  20. package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
  21. package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
  22. package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
  23. package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
  24. package/.claude/skills/setup/SKILL.md +218 -0
  25. package/.claude/skills/update-nanoclaw/SKILL.md +235 -0
  26. package/.claude/skills/update-skills/SKILL.md +130 -0
  27. package/.claude/skills/use-local-whisper/SKILL.md +152 -0
  28. package/.claude/skills/x-integration/SKILL.md +417 -0
  29. package/.claude/skills/x-integration/agent.ts +243 -0
  30. package/.claude/skills/x-integration/host.ts +159 -0
  31. package/.claude/skills/x-integration/lib/browser.ts +148 -0
  32. package/.claude/skills/x-integration/lib/config.ts +62 -0
  33. package/.claude/skills/x-integration/scripts/like.ts +56 -0
  34. package/.claude/skills/x-integration/scripts/post.ts +66 -0
  35. package/.claude/skills/x-integration/scripts/quote.ts +80 -0
  36. package/.claude/skills/x-integration/scripts/reply.ts +74 -0
  37. package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
  38. package/.claude/skills/x-integration/scripts/setup.ts +87 -0
  39. package/.env.example +1 -0
  40. package/.github/CODEOWNERS +10 -0
  41. package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  42. package/.github/workflows/bump-version.yml +32 -0
  43. package/.github/workflows/ci.yml +25 -0
  44. package/.github/workflows/merge-forward-skills.yml +160 -0
  45. package/.github/workflows/update-tokens.yml +42 -0
  46. package/.husky/pre-commit +1 -0
  47. package/.mcp.json +3 -0
  48. package/.nvmrc +1 -0
  49. package/.prettierrc +3 -0
  50. package/CHANGELOG.md +8 -0
  51. package/CLAUDE.md +64 -0
  52. package/CONTRIBUTING.md +23 -0
  53. package/CONTRIBUTORS.md +15 -0
  54. package/LICENSE +21 -0
  55. package/NanoClaw_with_Web-Support.md +290 -0
  56. package/README.md +261 -0
  57. package/README_zh.md +200 -0
  58. package/assets/nanoclaw-favicon.png +0 -0
  59. package/assets/nanoclaw-icon.png +0 -0
  60. package/assets/nanoclaw-logo-dark.png +0 -0
  61. package/assets/nanoclaw-logo.png +0 -0
  62. package/assets/nanoclaw-profile.jpeg +0 -0
  63. package/assets/nanoclaw-sales.png +0 -0
  64. package/assets/social-preview.jpg +0 -0
  65. package/config-examples/mount-allowlist.json +25 -0
  66. package/container/Dockerfile +70 -0
  67. package/container/agent-runner/package-lock.json +1524 -0
  68. package/container/agent-runner/package.json +21 -0
  69. package/container/agent-runner/src/index.ts +558 -0
  70. package/container/agent-runner/src/ipc-mcp-stdio.ts +338 -0
  71. package/container/agent-runner/tsconfig.json +15 -0
  72. package/container/build.sh +23 -0
  73. package/container/skills/agent-browser/SKILL.md +159 -0
  74. package/container/skills/capabilities/SKILL.md +100 -0
  75. package/container/skills/status/SKILL.md +104 -0
  76. package/dist/channels/index.d.ts +2 -0
  77. package/dist/channels/index.d.ts.map +1 -0
  78. package/dist/channels/index.js +9 -0
  79. package/dist/channels/index.js.map +1 -0
  80. package/dist/channels/registry.d.ts +13 -0
  81. package/dist/channels/registry.d.ts.map +1 -0
  82. package/dist/channels/registry.js +11 -0
  83. package/dist/channels/registry.js.map +1 -0
  84. package/dist/channels/registry.test.d.ts +2 -0
  85. package/dist/channels/registry.test.d.ts.map +1 -0
  86. package/dist/channels/registry.test.js +32 -0
  87. package/dist/channels/registry.test.js.map +1 -0
  88. package/dist/channels/web.d.ts +2 -0
  89. package/dist/channels/web.d.ts.map +1 -0
  90. package/dist/channels/web.js +1738 -0
  91. package/dist/channels/web.js.map +1 -0
  92. package/dist/cli.d.ts +11 -0
  93. package/dist/cli.d.ts.map +1 -0
  94. package/dist/cli.js +182 -0
  95. package/dist/cli.js.map +1 -0
  96. package/dist/config.d.ts +19 -0
  97. package/dist/config.d.ts.map +1 -0
  98. package/dist/config.js +36 -0
  99. package/dist/config.js.map +1 -0
  100. package/dist/container-runner.d.ts +44 -0
  101. package/dist/container-runner.d.ts.map +1 -0
  102. package/dist/container-runner.js +467 -0
  103. package/dist/container-runner.js.map +1 -0
  104. package/dist/container-runner.test.d.ts +2 -0
  105. package/dist/container-runner.test.d.ts.map +1 -0
  106. package/dist/container-runner.test.js +150 -0
  107. package/dist/container-runner.test.js.map +1 -0
  108. package/dist/container-runtime.d.ts +22 -0
  109. package/dist/container-runtime.d.ts.map +1 -0
  110. package/dist/container-runtime.js +96 -0
  111. package/dist/container-runtime.js.map +1 -0
  112. package/dist/container-runtime.test.d.ts +2 -0
  113. package/dist/container-runtime.test.d.ts.map +1 -0
  114. package/dist/container-runtime.test.js +93 -0
  115. package/dist/container-runtime.test.js.map +1 -0
  116. package/dist/credential-proxy.d.ts +21 -0
  117. package/dist/credential-proxy.d.ts.map +1 -0
  118. package/dist/credential-proxy.js +95 -0
  119. package/dist/credential-proxy.js.map +1 -0
  120. package/dist/credential-proxy.test.d.ts +2 -0
  121. package/dist/credential-proxy.test.d.ts.map +1 -0
  122. package/dist/credential-proxy.test.js +134 -0
  123. package/dist/credential-proxy.test.js.map +1 -0
  124. package/dist/db.d.ts +115 -0
  125. package/dist/db.d.ts.map +1 -0
  126. package/dist/db.js +549 -0
  127. package/dist/db.js.map +1 -0
  128. package/dist/db.test.d.ts +2 -0
  129. package/dist/db.test.d.ts.map +1 -0
  130. package/dist/db.test.js +360 -0
  131. package/dist/db.test.js.map +1 -0
  132. package/dist/env.d.ts +8 -0
  133. package/dist/env.d.ts.map +1 -0
  134. package/dist/env.js +42 -0
  135. package/dist/env.js.map +1 -0
  136. package/dist/formatting.test.d.ts +2 -0
  137. package/dist/formatting.test.d.ts.map +1 -0
  138. package/dist/formatting.test.js +183 -0
  139. package/dist/formatting.test.js.map +1 -0
  140. package/dist/group-folder.d.ts +5 -0
  141. package/dist/group-folder.d.ts.map +1 -0
  142. package/dist/group-folder.js +44 -0
  143. package/dist/group-folder.js.map +1 -0
  144. package/dist/group-folder.test.d.ts +2 -0
  145. package/dist/group-folder.test.d.ts.map +1 -0
  146. package/dist/group-folder.test.js +29 -0
  147. package/dist/group-folder.test.js.map +1 -0
  148. package/dist/group-queue.d.ts +34 -0
  149. package/dist/group-queue.d.ts.map +1 -0
  150. package/dist/group-queue.js +263 -0
  151. package/dist/group-queue.js.map +1 -0
  152. package/dist/group-queue.test.d.ts +2 -0
  153. package/dist/group-queue.test.d.ts.map +1 -0
  154. package/dist/group-queue.test.js +341 -0
  155. package/dist/group-queue.test.js.map +1 -0
  156. package/dist/index.d.ts +12 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +518 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/ipc-auth.test.d.ts +2 -0
  161. package/dist/ipc-auth.test.d.ts.map +1 -0
  162. package/dist/ipc-auth.test.js +434 -0
  163. package/dist/ipc-auth.test.js.map +1 -0
  164. package/dist/ipc.d.ts +32 -0
  165. package/dist/ipc.d.ts.map +1 -0
  166. package/dist/ipc.js +311 -0
  167. package/dist/ipc.js.map +1 -0
  168. package/dist/logger.d.ts +3 -0
  169. package/dist/logger.d.ts.map +1 -0
  170. package/dist/logger.js +14 -0
  171. package/dist/logger.js.map +1 -0
  172. package/dist/mount-security.d.ts +34 -0
  173. package/dist/mount-security.d.ts.map +1 -0
  174. package/dist/mount-security.js +325 -0
  175. package/dist/mount-security.js.map +1 -0
  176. package/dist/remote-control.d.ts +32 -0
  177. package/dist/remote-control.d.ts.map +1 -0
  178. package/dist/remote-control.js +185 -0
  179. package/dist/remote-control.js.map +1 -0
  180. package/dist/remote-control.test.d.ts +2 -0
  181. package/dist/remote-control.test.d.ts.map +1 -0
  182. package/dist/remote-control.test.js +321 -0
  183. package/dist/remote-control.test.js.map +1 -0
  184. package/dist/router.d.ts +8 -0
  185. package/dist/router.d.ts.map +1 -0
  186. package/dist/router.js +37 -0
  187. package/dist/router.js.map +1 -0
  188. package/dist/routing.test.d.ts +2 -0
  189. package/dist/routing.test.d.ts.map +1 -0
  190. package/dist/routing.test.js +81 -0
  191. package/dist/routing.test.js.map +1 -0
  192. package/dist/sender-allowlist.d.ts +14 -0
  193. package/dist/sender-allowlist.d.ts.map +1 -0
  194. package/dist/sender-allowlist.js +79 -0
  195. package/dist/sender-allowlist.js.map +1 -0
  196. package/dist/sender-allowlist.test.d.ts +2 -0
  197. package/dist/sender-allowlist.test.d.ts.map +1 -0
  198. package/dist/sender-allowlist.test.js +186 -0
  199. package/dist/sender-allowlist.test.js.map +1 -0
  200. package/dist/session-commands.d.ts +47 -0
  201. package/dist/session-commands.d.ts.map +1 -0
  202. package/dist/session-commands.js +102 -0
  203. package/dist/session-commands.js.map +1 -0
  204. package/dist/session-commands.test.d.ts +2 -0
  205. package/dist/session-commands.test.d.ts.map +1 -0
  206. package/dist/session-commands.test.js +190 -0
  207. package/dist/session-commands.test.js.map +1 -0
  208. package/dist/task-scheduler.d.ts +22 -0
  209. package/dist/task-scheduler.d.ts.map +1 -0
  210. package/dist/task-scheduler.js +210 -0
  211. package/dist/task-scheduler.js.map +1 -0
  212. package/dist/task-scheduler.test.d.ts +2 -0
  213. package/dist/task-scheduler.test.d.ts.map +1 -0
  214. package/dist/task-scheduler.test.js +107 -0
  215. package/dist/task-scheduler.test.js.map +1 -0
  216. package/dist/timezone.d.ts +6 -0
  217. package/dist/timezone.d.ts.map +1 -0
  218. package/dist/timezone.js +17 -0
  219. package/dist/timezone.js.map +1 -0
  220. package/dist/timezone.test.d.ts +2 -0
  221. package/dist/timezone.test.d.ts.map +1 -0
  222. package/dist/timezone.test.js +23 -0
  223. package/dist/timezone.test.js.map +1 -0
  224. package/dist/types.d.ts +78 -0
  225. package/dist/types.d.ts.map +1 -0
  226. package/dist/types.js +2 -0
  227. package/dist/types.js.map +1 -0
  228. package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
  229. package/docs/DEBUG_CHECKLIST.md +143 -0
  230. package/docs/REQUIREMENTS.md +196 -0
  231. package/docs/SDK_DEEP_DIVE.md +643 -0
  232. package/docs/SECURITY.md +122 -0
  233. package/docs/SPEC.md +785 -0
  234. package/docs/docker-sandboxes.md +359 -0
  235. package/docs/nanoclaw-architecture-final.md +1063 -0
  236. package/docs/nanorepo-architecture.md +168 -0
  237. package/docs/skills-as-branches.md +662 -0
  238. package/groups/global/CLAUDE.md +58 -0
  239. package/groups/main/CLAUDE.md +246 -0
  240. package/launchd/com.nanoclaw.plist +32 -0
  241. package/package.json +45 -0
  242. package/repo-tokens/README.md +113 -0
  243. package/repo-tokens/action.yml +186 -0
  244. package/repo-tokens/badge.svg +23 -0
  245. package/repo-tokens/examples/green.svg +14 -0
  246. package/repo-tokens/examples/red.svg +14 -0
  247. package/repo-tokens/examples/yellow-green.svg +14 -0
  248. package/repo-tokens/examples/yellow.svg +14 -0
  249. package/scripts/run-migrations.ts +105 -0
  250. package/setup/container.ts +144 -0
  251. package/setup/environment.test.ts +121 -0
  252. package/setup/environment.ts +94 -0
  253. package/setup/groups.ts +229 -0
  254. package/setup/index.ts +58 -0
  255. package/setup/mounts.ts +115 -0
  256. package/setup/platform.test.ts +120 -0
  257. package/setup/platform.ts +132 -0
  258. package/setup/register.test.ts +257 -0
  259. package/setup/register.ts +177 -0
  260. package/setup/service.test.ts +187 -0
  261. package/setup/service.ts +362 -0
  262. package/setup/status.ts +16 -0
  263. package/setup/verify.ts +192 -0
  264. package/setup.sh +161 -0
  265. package/src/channels/index.ts +12 -0
  266. package/src/channels/registry.test.ts +42 -0
  267. package/src/channels/registry.ts +32 -0
  268. package/src/channels/web.ts +1856 -0
  269. package/src/cli.ts +209 -0
  270. package/src/config.ts +73 -0
  271. package/src/container-runner.test.ts +210 -0
  272. package/src/container-runner.ts +707 -0
  273. package/src/container-runtime.test.ts +149 -0
  274. package/src/container-runtime.ts +127 -0
  275. package/src/credential-proxy.test.ts +192 -0
  276. package/src/credential-proxy.ts +125 -0
  277. package/src/db.test.ts +484 -0
  278. package/src/db.ts +803 -0
  279. package/src/env.ts +42 -0
  280. package/src/formatting.test.ts +256 -0
  281. package/src/group-folder.test.ts +43 -0
  282. package/src/group-folder.ts +44 -0
  283. package/src/group-queue.test.ts +484 -0
  284. package/src/group-queue.ts +365 -0
  285. package/src/index.ts +731 -0
  286. package/src/ipc-auth.test.ts +679 -0
  287. package/src/ipc.ts +461 -0
  288. package/src/logger.ts +16 -0
  289. package/src/mount-security.ts +419 -0
  290. package/src/remote-control.test.ts +397 -0
  291. package/src/remote-control.ts +224 -0
  292. package/src/router.ts +52 -0
  293. package/src/routing.test.ts +170 -0
  294. package/src/sender-allowlist.test.ts +216 -0
  295. package/src/sender-allowlist.ts +128 -0
  296. package/src/session-commands.test.ts +247 -0
  297. package/src/session-commands.ts +163 -0
  298. package/src/task-scheduler.test.ts +129 -0
  299. package/src/task-scheduler.ts +295 -0
  300. package/src/timezone.test.ts +29 -0
  301. package/src/timezone.ts +16 -0
  302. package/src/types.ts +107 -0
  303. package/tsconfig.json +20 -0
  304. package/vitest.config.ts +7 -0
  305. package/vitest.skills.config.ts +7 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Mount Security Module for NanoClaw
3
+ *
4
+ * Validates additional mounts against an allowlist stored OUTSIDE the project root.
5
+ * This prevents container agents from modifying security configuration.
6
+ *
7
+ * Allowlist location: ~/.config/nanoclaw/mount-allowlist.json
8
+ */
9
+ import fs from 'fs';
10
+ import os from 'os';
11
+ import path from 'path';
12
+ import pino from 'pino';
13
+ import { MOUNT_ALLOWLIST_PATH } from './config.js';
14
+ const logger = pino({
15
+ level: process.env.LOG_LEVEL || 'info',
16
+ transport: { target: 'pino-pretty', options: { colorize: true } },
17
+ });
18
+ // Cache the allowlist in memory - only reloads on process restart
19
+ let cachedAllowlist = null;
20
+ let allowlistLoadError = null;
21
+ /**
22
+ * Default blocked patterns - paths that should never be mounted
23
+ */
24
+ const DEFAULT_BLOCKED_PATTERNS = [
25
+ '.ssh',
26
+ '.gnupg',
27
+ '.gpg',
28
+ '.aws',
29
+ '.azure',
30
+ '.gcloud',
31
+ '.kube',
32
+ '.docker',
33
+ 'credentials',
34
+ '.env',
35
+ '.netrc',
36
+ '.npmrc',
37
+ '.pypirc',
38
+ 'id_rsa',
39
+ 'id_ed25519',
40
+ 'private_key',
41
+ '.secret',
42
+ ];
43
+ /**
44
+ * Load the mount allowlist from the external config location.
45
+ * Returns null if the file doesn't exist or is invalid.
46
+ * Result is cached in memory for the lifetime of the process.
47
+ */
48
+ export function loadMountAllowlist() {
49
+ if (cachedAllowlist !== null) {
50
+ return cachedAllowlist;
51
+ }
52
+ if (allowlistLoadError !== null) {
53
+ // Already tried and failed, don't spam logs
54
+ return null;
55
+ }
56
+ try {
57
+ if (!fs.existsSync(MOUNT_ALLOWLIST_PATH)) {
58
+ allowlistLoadError = `Mount allowlist not found at ${MOUNT_ALLOWLIST_PATH}`;
59
+ logger.warn({ path: MOUNT_ALLOWLIST_PATH }, 'Mount allowlist not found - additional mounts will be BLOCKED. ' +
60
+ 'Create the file to enable additional mounts.');
61
+ return null;
62
+ }
63
+ const content = fs.readFileSync(MOUNT_ALLOWLIST_PATH, 'utf-8');
64
+ const allowlist = JSON.parse(content);
65
+ // Validate structure
66
+ if (!Array.isArray(allowlist.allowedRoots)) {
67
+ throw new Error('allowedRoots must be an array');
68
+ }
69
+ if (!Array.isArray(allowlist.blockedPatterns)) {
70
+ throw new Error('blockedPatterns must be an array');
71
+ }
72
+ if (typeof allowlist.nonMainReadOnly !== 'boolean') {
73
+ throw new Error('nonMainReadOnly must be a boolean');
74
+ }
75
+ // Merge with default blocked patterns
76
+ const mergedBlockedPatterns = [
77
+ ...new Set([...DEFAULT_BLOCKED_PATTERNS, ...allowlist.blockedPatterns]),
78
+ ];
79
+ allowlist.blockedPatterns = mergedBlockedPatterns;
80
+ cachedAllowlist = allowlist;
81
+ logger.info({
82
+ path: MOUNT_ALLOWLIST_PATH,
83
+ allowedRoots: allowlist.allowedRoots.length,
84
+ blockedPatterns: allowlist.blockedPatterns.length,
85
+ }, 'Mount allowlist loaded successfully');
86
+ return cachedAllowlist;
87
+ }
88
+ catch (err) {
89
+ allowlistLoadError = err instanceof Error ? err.message : String(err);
90
+ logger.error({
91
+ path: MOUNT_ALLOWLIST_PATH,
92
+ error: allowlistLoadError,
93
+ }, 'Failed to load mount allowlist - additional mounts will be BLOCKED');
94
+ return null;
95
+ }
96
+ }
97
+ /**
98
+ * Expand ~ to home directory and resolve to absolute path
99
+ */
100
+ function expandPath(p) {
101
+ const homeDir = process.env.HOME || os.homedir();
102
+ if (p.startsWith('~/')) {
103
+ return path.join(homeDir, p.slice(2));
104
+ }
105
+ if (p === '~') {
106
+ return homeDir;
107
+ }
108
+ return path.resolve(p);
109
+ }
110
+ /**
111
+ * Get the real path, resolving symlinks.
112
+ * Returns null if the path doesn't exist.
113
+ */
114
+ function getRealPath(p) {
115
+ try {
116
+ return fs.realpathSync(p);
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ }
122
+ /**
123
+ * Check if a path matches any blocked pattern
124
+ */
125
+ function matchesBlockedPattern(realPath, blockedPatterns) {
126
+ const pathParts = realPath.split(path.sep);
127
+ for (const pattern of blockedPatterns) {
128
+ // Check if any path component matches the pattern
129
+ for (const part of pathParts) {
130
+ if (part === pattern || part.includes(pattern)) {
131
+ return pattern;
132
+ }
133
+ }
134
+ // Also check if the full path contains the pattern
135
+ if (realPath.includes(pattern)) {
136
+ return pattern;
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ /**
142
+ * Check if a real path is under an allowed root
143
+ */
144
+ function findAllowedRoot(realPath, allowedRoots) {
145
+ for (const root of allowedRoots) {
146
+ const expandedRoot = expandPath(root.path);
147
+ const realRoot = getRealPath(expandedRoot);
148
+ if (realRoot === null) {
149
+ // Allowed root doesn't exist, skip it
150
+ continue;
151
+ }
152
+ // Check if realPath is under realRoot
153
+ const relative = path.relative(realRoot, realPath);
154
+ if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
155
+ return root;
156
+ }
157
+ }
158
+ return null;
159
+ }
160
+ /**
161
+ * Validate the container path to prevent escaping /workspace/extra/
162
+ */
163
+ function isValidContainerPath(containerPath) {
164
+ // Must not contain .. to prevent path traversal
165
+ if (containerPath.includes('..')) {
166
+ return false;
167
+ }
168
+ // Must not be absolute (it will be prefixed with /workspace/extra/)
169
+ if (containerPath.startsWith('/')) {
170
+ return false;
171
+ }
172
+ // Must not be empty
173
+ if (!containerPath || containerPath.trim() === '') {
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+ /**
179
+ * Validate a single additional mount against the allowlist.
180
+ * Returns validation result with reason.
181
+ */
182
+ export function validateMount(mount, isMain) {
183
+ const allowlist = loadMountAllowlist();
184
+ // If no allowlist, block all additional mounts
185
+ if (allowlist === null) {
186
+ return {
187
+ allowed: false,
188
+ reason: `No mount allowlist configured at ${MOUNT_ALLOWLIST_PATH}`,
189
+ };
190
+ }
191
+ // Derive containerPath from hostPath basename if not specified
192
+ const containerPath = mount.containerPath || path.basename(mount.hostPath);
193
+ // Validate container path (cheap check)
194
+ if (!isValidContainerPath(containerPath)) {
195
+ return {
196
+ allowed: false,
197
+ reason: `Invalid container path: "${containerPath}" - must be relative, non-empty, and not contain ".."`,
198
+ };
199
+ }
200
+ // Expand and resolve the host path
201
+ const expandedPath = expandPath(mount.hostPath);
202
+ const realPath = getRealPath(expandedPath);
203
+ if (realPath === null) {
204
+ return {
205
+ allowed: false,
206
+ reason: `Host path does not exist: "${mount.hostPath}" (expanded: "${expandedPath}")`,
207
+ };
208
+ }
209
+ // Check against blocked patterns
210
+ const blockedMatch = matchesBlockedPattern(realPath, allowlist.blockedPatterns);
211
+ if (blockedMatch !== null) {
212
+ return {
213
+ allowed: false,
214
+ reason: `Path matches blocked pattern "${blockedMatch}": "${realPath}"`,
215
+ };
216
+ }
217
+ // Check if under an allowed root
218
+ const allowedRoot = findAllowedRoot(realPath, allowlist.allowedRoots);
219
+ if (allowedRoot === null) {
220
+ return {
221
+ allowed: false,
222
+ reason: `Path "${realPath}" is not under any allowed root. Allowed roots: ${allowlist.allowedRoots
223
+ .map((r) => expandPath(r.path))
224
+ .join(', ')}`,
225
+ };
226
+ }
227
+ // Determine effective readonly status
228
+ const requestedReadWrite = mount.readonly === false;
229
+ let effectiveReadonly = true; // Default to readonly
230
+ if (requestedReadWrite) {
231
+ if (!isMain && allowlist.nonMainReadOnly) {
232
+ // Non-main groups forced to read-only
233
+ effectiveReadonly = true;
234
+ logger.info({
235
+ mount: mount.hostPath,
236
+ }, 'Mount forced to read-only for non-main group');
237
+ }
238
+ else if (!allowedRoot.allowReadWrite) {
239
+ // Root doesn't allow read-write
240
+ effectiveReadonly = true;
241
+ logger.info({
242
+ mount: mount.hostPath,
243
+ root: allowedRoot.path,
244
+ }, 'Mount forced to read-only - root does not allow read-write');
245
+ }
246
+ else {
247
+ // Read-write allowed
248
+ effectiveReadonly = false;
249
+ }
250
+ }
251
+ return {
252
+ allowed: true,
253
+ reason: `Allowed under root "${allowedRoot.path}"${allowedRoot.description ? ` (${allowedRoot.description})` : ''}`,
254
+ realHostPath: realPath,
255
+ resolvedContainerPath: containerPath,
256
+ effectiveReadonly,
257
+ };
258
+ }
259
+ /**
260
+ * Validate all additional mounts for a group.
261
+ * Returns array of validated mounts (only those that passed validation).
262
+ * Logs warnings for rejected mounts.
263
+ */
264
+ export function validateAdditionalMounts(mounts, groupName, isMain) {
265
+ const validatedMounts = [];
266
+ for (const mount of mounts) {
267
+ const result = validateMount(mount, isMain);
268
+ if (result.allowed) {
269
+ validatedMounts.push({
270
+ hostPath: result.realHostPath,
271
+ containerPath: `/workspace/extra/${result.resolvedContainerPath}`,
272
+ readonly: result.effectiveReadonly,
273
+ });
274
+ logger.debug({
275
+ group: groupName,
276
+ hostPath: result.realHostPath,
277
+ containerPath: result.resolvedContainerPath,
278
+ readonly: result.effectiveReadonly,
279
+ reason: result.reason,
280
+ }, 'Mount validated successfully');
281
+ }
282
+ else {
283
+ logger.warn({
284
+ group: groupName,
285
+ requestedPath: mount.hostPath,
286
+ containerPath: mount.containerPath,
287
+ reason: result.reason,
288
+ }, 'Additional mount REJECTED');
289
+ }
290
+ }
291
+ return validatedMounts;
292
+ }
293
+ /**
294
+ * Generate a template allowlist file for users to customize
295
+ */
296
+ export function generateAllowlistTemplate() {
297
+ const template = {
298
+ allowedRoots: [
299
+ {
300
+ path: '~/projects',
301
+ allowReadWrite: true,
302
+ description: 'Development projects',
303
+ },
304
+ {
305
+ path: '~/repos',
306
+ allowReadWrite: true,
307
+ description: 'Git repositories',
308
+ },
309
+ {
310
+ path: '~/Documents/work',
311
+ allowReadWrite: false,
312
+ description: 'Work documents (read-only)',
313
+ },
314
+ ],
315
+ blockedPatterns: [
316
+ // Additional patterns beyond defaults
317
+ 'password',
318
+ 'secret',
319
+ 'token',
320
+ ],
321
+ nonMainReadOnly: true,
322
+ };
323
+ return JSON.stringify(template, null, 2);
324
+ }
325
+ //# sourceMappingURL=mount-security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-security.js","sourceRoot":"","sources":["../src/mount-security.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ interface RemoteControlSession {
2
+ pid: number;
3
+ url: string;
4
+ startedBy: string;
5
+ startedInChat: string;
6
+ startedAt: string;
7
+ }
8
+ /**
9
+ * Restore session from disk on startup.
10
+ * If the process is still alive, adopt it. Otherwise, clean up.
11
+ */
12
+ export declare function restoreRemoteControl(): void;
13
+ export declare function getActiveSession(): RemoteControlSession | null;
14
+ /** @internal — exported for testing only */
15
+ export declare function _resetForTesting(): void;
16
+ /** @internal — exported for testing only */
17
+ export declare function _getStateFilePath(): string;
18
+ export declare function startRemoteControl(sender: string, chatJid: string, cwd: string): Promise<{
19
+ ok: true;
20
+ url: string;
21
+ } | {
22
+ ok: false;
23
+ error: string;
24
+ }>;
25
+ export declare function stopRemoteControl(): {
26
+ ok: true;
27
+ } | {
28
+ ok: false;
29
+ error: string;
30
+ };
31
+ export {};
32
+ //# sourceMappingURL=remote-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-control.d.ts","sourceRoot":"","sources":["../src/remote-control.ts"],"names":[],"mappings":"AAOA,UAAU,oBAAoB;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAiCD;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAsB3C;AAED,wBAAgB,gBAAgB,IAAI,oBAAoB,GAAG,IAAI,CAE9D;AAED,4CAA4C;AAC5C,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,4CAA4C;AAC5C,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA8GnE;AAED,wBAAgB,iBAAiB,IAC7B;IACE,EAAE,EAAE,IAAI,CAAC;CACV,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAe/B"}
@@ -0,0 +1,185 @@
1
+ import { spawn } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { DATA_DIR } from './config.js';
5
+ import { logger } from './logger.js';
6
+ let activeSession = null;
7
+ const URL_REGEX = /https:\/\/claude\.ai\/code\S+/;
8
+ const URL_TIMEOUT_MS = 30_000;
9
+ const URL_POLL_MS = 200;
10
+ const STATE_FILE = path.join(DATA_DIR, 'remote-control.json');
11
+ const STDOUT_FILE = path.join(DATA_DIR, 'remote-control.stdout');
12
+ const STDERR_FILE = path.join(DATA_DIR, 'remote-control.stderr');
13
+ function saveState(session) {
14
+ fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
15
+ fs.writeFileSync(STATE_FILE, JSON.stringify(session));
16
+ }
17
+ function clearState() {
18
+ try {
19
+ fs.unlinkSync(STATE_FILE);
20
+ }
21
+ catch {
22
+ // ignore
23
+ }
24
+ }
25
+ function isProcessAlive(pid) {
26
+ try {
27
+ process.kill(pid, 0);
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ /**
35
+ * Restore session from disk on startup.
36
+ * If the process is still alive, adopt it. Otherwise, clean up.
37
+ */
38
+ export function restoreRemoteControl() {
39
+ let data;
40
+ try {
41
+ data = fs.readFileSync(STATE_FILE, 'utf-8');
42
+ }
43
+ catch {
44
+ return;
45
+ }
46
+ try {
47
+ const session = JSON.parse(data);
48
+ if (session.pid && isProcessAlive(session.pid)) {
49
+ activeSession = session;
50
+ logger.info({ pid: session.pid, url: session.url }, 'Restored Remote Control session from previous run');
51
+ }
52
+ else {
53
+ clearState();
54
+ }
55
+ }
56
+ catch {
57
+ clearState();
58
+ }
59
+ }
60
+ export function getActiveSession() {
61
+ return activeSession;
62
+ }
63
+ /** @internal — exported for testing only */
64
+ export function _resetForTesting() {
65
+ activeSession = null;
66
+ }
67
+ /** @internal — exported for testing only */
68
+ export function _getStateFilePath() {
69
+ return STATE_FILE;
70
+ }
71
+ export async function startRemoteControl(sender, chatJid, cwd) {
72
+ if (activeSession) {
73
+ // Verify the process is still alive
74
+ if (isProcessAlive(activeSession.pid)) {
75
+ return { ok: true, url: activeSession.url };
76
+ }
77
+ // Process died — clean up and start a new one
78
+ activeSession = null;
79
+ clearState();
80
+ }
81
+ // Redirect stdout/stderr to files so the process has no pipes to the parent.
82
+ // This prevents SIGPIPE when NanoClaw restarts.
83
+ fs.mkdirSync(DATA_DIR, { recursive: true });
84
+ const stdoutFd = fs.openSync(STDOUT_FILE, 'w');
85
+ const stderrFd = fs.openSync(STDERR_FILE, 'w');
86
+ let proc;
87
+ try {
88
+ proc = spawn('claude', ['remote-control', '--name', 'NanoClaw Remote'], {
89
+ cwd,
90
+ stdio: ['pipe', stdoutFd, stderrFd],
91
+ detached: true,
92
+ });
93
+ }
94
+ catch (err) {
95
+ fs.closeSync(stdoutFd);
96
+ fs.closeSync(stderrFd);
97
+ return { ok: false, error: `Failed to start: ${err.message}` };
98
+ }
99
+ // Auto-accept the "Enable Remote Control?" prompt
100
+ if (proc.stdin) {
101
+ proc.stdin.write('y\n');
102
+ proc.stdin.end();
103
+ }
104
+ // Close FDs in the parent — the child inherited copies
105
+ fs.closeSync(stdoutFd);
106
+ fs.closeSync(stderrFd);
107
+ // Fully detach from parent
108
+ proc.unref();
109
+ const pid = proc.pid;
110
+ if (!pid) {
111
+ return { ok: false, error: 'Failed to get process PID' };
112
+ }
113
+ // Poll the stdout file for the URL
114
+ return new Promise((resolve) => {
115
+ const startTime = Date.now();
116
+ const poll = () => {
117
+ // Check if process died
118
+ if (!isProcessAlive(pid)) {
119
+ resolve({ ok: false, error: 'Process exited before producing URL' });
120
+ return;
121
+ }
122
+ // Check for URL in stdout file
123
+ let content = '';
124
+ try {
125
+ content = fs.readFileSync(STDOUT_FILE, 'utf-8');
126
+ }
127
+ catch {
128
+ // File might not have content yet
129
+ }
130
+ const match = content.match(URL_REGEX);
131
+ if (match) {
132
+ const session = {
133
+ pid,
134
+ url: match[0],
135
+ startedBy: sender,
136
+ startedInChat: chatJid,
137
+ startedAt: new Date().toISOString(),
138
+ };
139
+ activeSession = session;
140
+ saveState(session);
141
+ logger.info({ url: match[0], pid, sender, chatJid }, 'Remote Control session started');
142
+ resolve({ ok: true, url: match[0] });
143
+ return;
144
+ }
145
+ // Timeout check
146
+ if (Date.now() - startTime >= URL_TIMEOUT_MS) {
147
+ try {
148
+ process.kill(-pid, 'SIGTERM');
149
+ }
150
+ catch {
151
+ try {
152
+ process.kill(pid, 'SIGTERM');
153
+ }
154
+ catch {
155
+ // already dead
156
+ }
157
+ }
158
+ resolve({
159
+ ok: false,
160
+ error: 'Timed out waiting for Remote Control URL',
161
+ });
162
+ return;
163
+ }
164
+ setTimeout(poll, URL_POLL_MS);
165
+ };
166
+ poll();
167
+ });
168
+ }
169
+ export function stopRemoteControl() {
170
+ if (!activeSession) {
171
+ return { ok: false, error: 'No active Remote Control session' };
172
+ }
173
+ const { pid } = activeSession;
174
+ try {
175
+ process.kill(pid, 'SIGTERM');
176
+ }
177
+ catch {
178
+ // already dead
179
+ }
180
+ activeSession = null;
181
+ clearState();
182
+ logger.info({ pid }, 'Remote Control session stopped');
183
+ return { ok: true };
184
+ }
185
+ //# sourceMappingURL=remote-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-control.js","sourceRoot":"","sources":["../src/remote-control.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=remote-control.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-control.test.d.ts","sourceRoot":"","sources":["../src/remote-control.test.ts"],"names":[],"mappings":""}