@causyne/agent 0.0.0-stage → 1.10.1

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 (274) hide show
  1. package/LICENSE +102 -0
  2. package/README.md +31 -2
  3. package/SECURITY.md +24 -0
  4. package/bin/causyne.js +13 -0
  5. package/docs/ARCHITECTURE.md +63 -0
  6. package/docs/PROVENANCE.md +29 -0
  7. package/docs/SECURITY_REVIEW.md +89 -0
  8. package/docs/THIRD_PARTY_NOTICES.md +49 -0
  9. package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
  10. package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
  11. package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
  12. package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
  13. package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
  14. package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
  15. package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
  16. package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
  17. package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
  18. package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
  19. package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
  20. package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
  21. package/native-skills/android/play-store-release/SKILL.md +43 -0
  22. package/native-skills/android/room-and-persistence/SKILL.md +40 -0
  23. package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
  24. package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
  25. package/native-skills/backend/caching-strategy/SKILL.md +44 -0
  26. package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
  27. package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
  28. package/native-skills/backend/rest-api-design/SKILL.md +40 -0
  29. package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
  30. package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
  31. package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
  32. package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
  33. package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
  34. package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
  35. package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
  36. package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
  37. package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
  38. package/native-skills/data/metric-definition/SKILL.md +42 -0
  39. package/native-skills/data/notebook-to-production/SKILL.md +43 -0
  40. package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
  41. package/native-skills/database/connection-pooling/SKILL.md +42 -0
  42. package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
  43. package/native-skills/database/query-optimisation/SKILL.md +38 -0
  44. package/native-skills/database/safe-migrations/SKILL.md +47 -0
  45. package/native-skills/database/schema-design/SKILL.md +38 -0
  46. package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
  47. package/native-skills/design/colour-system-construction/SKILL.md +38 -0
  48. package/native-skills/design/design-direction-brief/SKILL.md +41 -0
  49. package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
  50. package/native-skills/design/responsive-strategy/SKILL.md +39 -0
  51. package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
  52. package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
  53. package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
  54. package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
  55. package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
  56. package/native-skills/devops/incident-triage/SKILL.md +38 -0
  57. package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
  58. package/native-skills/devops/observability-baseline/SKILL.md +38 -0
  59. package/native-skills/devops/secret-management/SKILL.md +40 -0
  60. package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
  61. package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
  62. package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
  63. package/native-skills/git/branch-strategy/SKILL.md +39 -0
  64. package/native-skills/git/commit-hygiene/SKILL.md +41 -0
  65. package/native-skills/git/history-recovery/SKILL.md +44 -0
  66. package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
  67. package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
  68. package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
  69. package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
  70. package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
  71. package/native-skills/mobile/deep-linking/SKILL.md +39 -0
  72. package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
  73. package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
  74. package/native-skills/mobile/push-notifications/SKILL.md +38 -0
  75. package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
  76. package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
  77. package/native-skills/performance/load-testing/SKILL.md +42 -0
  78. package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
  79. package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
  80. package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
  81. package/native-skills/python/async-correctness/SKILL.md +40 -0
  82. package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
  83. package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
  84. package/native-skills/python/environment-isolation/SKILL.md +41 -0
  85. package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
  86. package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
  87. package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
  88. package/native-skills/security/authorization-model/SKILL.md +42 -0
  89. package/native-skills/security/crypto-usage/SKILL.md +48 -0
  90. package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
  91. package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
  92. package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
  93. package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
  94. package/native-skills/security/threat-modelling/SKILL.md +46 -0
  95. package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
  96. package/native-skills/testing/property-based-testing/SKILL.md +46 -0
  97. package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
  98. package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
  99. package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
  100. package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
  101. package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
  102. package/native-skills/web/accessible-components/SKILL.md +45 -0
  103. package/native-skills/web/api-client-resilience/SKILL.md +39 -0
  104. package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
  105. package/native-skills/web/core-web-vitals/SKILL.md +40 -0
  106. package/native-skills/web/css-layout-systems/SKILL.md +41 -0
  107. package/native-skills/web/form-validation-ux/SKILL.md +39 -0
  108. package/native-skills/web/react-state-architecture/SKILL.md +41 -0
  109. package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
  110. package/package.json +68 -4
  111. package/src/agentLoop.js +2458 -0
  112. package/src/appChannel.js +109 -0
  113. package/src/approvalGrants.js +66 -0
  114. package/src/auditLog.js +52 -0
  115. package/src/autoUpdate.js +12 -0
  116. package/src/capabilities.js +36 -0
  117. package/src/checkpoints.js +1199 -0
  118. package/src/cliMode.js +638 -0
  119. package/src/commands.js +805 -0
  120. package/src/compiledSkills.js +540 -0
  121. package/src/config.js +130 -0
  122. package/src/contextManager.js +498 -0
  123. package/src/cortex/autoCondenser.js +192 -0
  124. package/src/cortex/provisioner.js +27 -0
  125. package/src/cortex/vectorDb.js +447 -0
  126. package/src/designPalettes.js +99 -0
  127. package/src/devMode.js +6 -0
  128. package/src/embodimentSession.js +180 -0
  129. package/src/entrypoint.js +194 -0
  130. package/src/execSafe.js +21 -0
  131. package/src/gitSnapshot.js +378 -0
  132. package/src/mcp/jsonRpc.js +165 -0
  133. package/src/mcp/mcpClient.js +136 -0
  134. package/src/mcp/mcpRegistry.js +207 -0
  135. package/src/memory.js +62 -0
  136. package/src/memoryManager.js +124 -0
  137. package/src/modelCatalogue.js +132 -0
  138. package/src/modelIds.js +42 -0
  139. package/src/modelRouter.js +145 -0
  140. package/src/outputStore.js +246 -0
  141. package/src/perception.js +124 -0
  142. package/src/planCache.js +437 -0
  143. package/src/procSignal.js +32 -0
  144. package/src/providers/anthropic.js +408 -0
  145. package/src/providers/base.js +328 -0
  146. package/src/providers/dashscope-byok.js +86 -0
  147. package/src/providers/deepseek.js +80 -0
  148. package/src/providers/factory.js +37 -0
  149. package/src/providers/gemini.js +346 -0
  150. package/src/providers/moonshot.js +73 -0
  151. package/src/providers/muse.js +169 -0
  152. package/src/providers/ollama.js +104 -0
  153. package/src/providers/openai-compatible.js +84 -0
  154. package/src/providers/openai.js +20 -0
  155. package/src/providers/qwen-byok.js +76 -0
  156. package/src/providers/xai.js +73 -0
  157. package/src/provisioning/capabilityConsent.js +24 -0
  158. package/src/provisioning/capabilityProvisioner.js +271 -0
  159. package/src/provisioning/capabilityTools.js +29 -0
  160. package/src/reporting.js +373 -0
  161. package/src/sandbox.js +529 -0
  162. package/src/sessionStore.js +279 -0
  163. package/src/skillsManager.js +288 -0
  164. package/src/slopDetector.js +307 -0
  165. package/src/taskBuffer.js +45 -0
  166. package/src/taskShape.js +241 -0
  167. package/src/textDiff.js +150 -0
  168. package/src/tokenTracker.js +560 -0
  169. package/src/tool-scripts/ax_snapshot.applescript +39 -0
  170. package/src/tool-scripts/ax_snapshot.ps1 +10 -0
  171. package/src/tool-scripts/desktop_act.applescript +58 -0
  172. package/src/tool-scripts/resolve_automator.py +119 -0
  173. package/src/toolPacks.js +524 -0
  174. package/src/tools/archive.js +63 -0
  175. package/src/tools/astryxOracle.js +78 -0
  176. package/src/tools/axSnapshot.js +165 -0
  177. package/src/tools/blenderAutomator.js +349 -0
  178. package/src/tools/browserAutomator.js +252 -0
  179. package/src/tools/checkSystemSpecs.js +64 -0
  180. package/src/tools/clipboard.js +36 -0
  181. package/src/tools/codeSearchAst.js +101 -0
  182. package/src/tools/codeSearcher.js +230 -0
  183. package/src/tools/codeTools.js +262 -0
  184. package/src/tools/codebaseSearch.js +75 -0
  185. package/src/tools/createSkill.js +129 -0
  186. package/src/tools/deferredMcpSearch.js +246 -0
  187. package/src/tools/desktopAct.js +208 -0
  188. package/src/tools/desktopOpen.js +34 -0
  189. package/src/tools/dynamicLoader.js +44 -0
  190. package/src/tools/executeProceduralMedia.js +93 -0
  191. package/src/tools/fsDelete.js +51 -0
  192. package/src/tools/fsEdit.js +105 -0
  193. package/src/tools/fsFind.js +128 -0
  194. package/src/tools/fsList.js +77 -0
  195. package/src/tools/fsMove.js +57 -0
  196. package/src/tools/fsRead.js +87 -0
  197. package/src/tools/fsSearch.js +198 -0
  198. package/src/tools/fsWrite.js +41 -0
  199. package/src/tools/gameAiBehaviorTree.js +208 -0
  200. package/src/tools/gameAnimationRig.js +194 -0
  201. package/src/tools/gameAssetFetcher.js +372 -0
  202. package/src/tools/gameAssetHarvester.js +210 -0
  203. package/src/tools/gameAudioSynth.js +267 -0
  204. package/src/tools/gameBuilder.js +886 -0
  205. package/src/tools/gameCanvasAudit.js +101 -0
  206. package/src/tools/gameCinematicSequencer.js +151 -0
  207. package/src/tools/gameDataForge.js +124 -0
  208. package/src/tools/gameDialogueTreeGen.js +166 -0
  209. package/src/tools/gameEconomyBalancer.js +161 -0
  210. package/src/tools/gameEntitySpawner.js +207 -0
  211. package/src/tools/gameLoreForge.js +153 -0
  212. package/src/tools/gameQuestArcBuilder.js +152 -0
  213. package/src/tools/gameSceneBuilder.js +158 -0
  214. package/src/tools/gameShaderForge.js +181 -0
  215. package/src/tools/gameTilemapGenerator.js +164 -0
  216. package/src/tools/gameUiHudBuilder.js +292 -0
  217. package/src/tools/gameVfxEmitterGen.js +108 -0
  218. package/src/tools/gameWebRuntimeInspector.js +91 -0
  219. package/src/tools/generateMediaViaApi.js +358 -0
  220. package/src/tools/gitExec.js +54 -0
  221. package/src/tools/godotAutomator.js +437 -0
  222. package/src/tools/manageLocalAiDependencies.js +220 -0
  223. package/src/tools/memoryWrite.js +99 -0
  224. package/src/tools/multiEdit.js +107 -0
  225. package/src/tools/palmierAutomator.js +104 -0
  226. package/src/tools/pdfReader.js +112 -0
  227. package/src/tools/persistentShell.js +148 -0
  228. package/src/tools/printer.js +316 -0
  229. package/src/tools/procTerm.js +91 -0
  230. package/src/tools/processList.js +52 -0
  231. package/src/tools/readNativeSkill.js +39 -0
  232. package/src/tools/registry.js +277 -0
  233. package/src/tools/resolveAutomator.js +102 -0
  234. package/src/tools/screenshot.js +75 -0
  235. package/src/tools/subagentOrchestrator.js +305 -0
  236. package/src/tools/sysRun.js +125 -0
  237. package/src/tools/todoWrite.js +75 -0
  238. package/src/tools/toolDispatch.js +127 -0
  239. package/src/tools/toolForge.js +124 -0
  240. package/src/tools/updatePlan.js +88 -0
  241. package/src/tools/viewImage.js +82 -0
  242. package/src/tools/visionInspector.js +154 -0
  243. package/src/tools/webFetch.js +74 -0
  244. package/src/tools/webSearch.js +82 -0
  245. package/src/tui/ask.js +164 -0
  246. package/src/tui/banner.js +158 -0
  247. package/src/tui/block.js +152 -0
  248. package/src/tui/chatRenderer.js +953 -0
  249. package/src/tui/completion.js +424 -0
  250. package/src/tui/dialogs.js +606 -0
  251. package/src/tui/diff.js +296 -0
  252. package/src/tui/history.js +135 -0
  253. package/src/tui/home.js +217 -0
  254. package/src/tui/invocation.js +105 -0
  255. package/src/tui/keys.js +443 -0
  256. package/src/tui/lineEditor.js +256 -0
  257. package/src/tui/overlay.js +256 -0
  258. package/src/tui/prompt.js +1258 -0
  259. package/src/tui/selectList.js +335 -0
  260. package/src/tui/stage.js +330 -0
  261. package/src/tui/stageSurface.js +609 -0
  262. package/src/tui/statusLine.js +254 -0
  263. package/src/tui/text.js +345 -0
  264. package/src/tui/theme.js +393 -0
  265. package/src/tui/toolDisplay.js +176 -0
  266. package/src/tui/transcript.js +235 -0
  267. package/src/tui/viewport.js +337 -0
  268. package/src/uncertainty.js +271 -0
  269. package/src/userSkills.js +265 -0
  270. package/src/utils/cli_ui.js +0 -0
  271. package/src/utils/inquirer.js +12 -0
  272. package/src/utils/paths.js +37 -0
  273. package/src/version.js +38 -0
  274. package/src/vfeTracker.js +220 -0
package/src/sandbox.js ADDED
@@ -0,0 +1,529 @@
1
+ import path from 'path';
2
+ import os from 'os';
3
+ import { loadConfig } from './config.js';
4
+ import { isDevEmulator } from './devMode.js';
5
+
6
+
7
+ function maskQuotedSpans(text) {
8
+ return text
9
+ .replace(/"(?:[^"\\]|\\.)*"/g, '""')
10
+ .replace(/'(?:[^'\\]|\\.)*'/g, "''")
11
+ .replace(/`(?:[^`\\]|\\.)*`/g, '``');
12
+ }
13
+
14
+ /**
15
+ * Split a command line into the individual commands the shell will run.
16
+ *
17
+ * Prefix matching only ever inspected the FIRST word of the whole line, so any
18
+ * leading token disguised everything after it: `cd ~/Documents && rm -f x` was
19
+ * classified from `cd` and auto-executed with no approval. Each segment must be
20
+ * judged on its own.
21
+ */
22
+ function splitShellSegments(text) {
23
+ return text
24
+ .split(/&&|\|\||[;\n|&]/)
25
+ .map((segment) => segment.trim())
26
+ .filter((segment) => segment.length > 0);
27
+ }
28
+
29
+ /**
30
+ * Read-only invocations of tools whose NAME is dangerous but whose subcommand
31
+ * is not. Keyed by the leading word; the pattern must match the whole segment.
32
+ *
33
+ * WHY THIS EXISTS. `brew` is in DANGEROUS_PREFIXES because `brew install` runs
34
+ * arbitrary install scripts. The prefix rule cannot tell that from
35
+ * `brew --version`, so asking Blender's version escalated a chain to
36
+ * DANGEROUS and put a red approval modal in front of the user. That is not a
37
+ * cosmetic problem: a gate that cries wolf on `--version` is a gate people
38
+ * learn to approve without reading, and then it is not a gate at all.
39
+ *
40
+ * THE RULE FOR ADDING TO THIS TABLE: the subcommand must be incapable of
41
+ * changing anything — no install, no remove, no start/stop, no write. When in
42
+ * doubt, leave it out; a false alarm is cheap and a missed one is not.
43
+ *
44
+ * `sudo` and `su` are deliberately absent and must stay absent: `sudo brew
45
+ * --version` is a privilege escalation whatever follows it.
46
+ */
47
+ const READ_ONLY_INVOCATIONS = {
48
+ brew: /^brew\s+(--version|-v|list|info|search|config|outdated|deps|home|--prefix|--cellar)\b/,
49
+ apt: /^apt\s+(--version|list|show|search|policy)\b/,
50
+ 'apt-get': /^apt-get\s+(--version|-v)\b/,
51
+ yum: /^yum\s+(--version|list|info|search)\b/,
52
+ dnf: /^dnf\s+(--version|list|info|search)\b/,
53
+ systemctl: /^systemctl\s+(status|show|cat|is-active|is-enabled|is-failed|list-units|list-unit-files|--version)\b/,
54
+ service: /^service\s+[\w.@-]+\s+status\b/,
55
+ crontab: /^crontab\s+-l\b/,
56
+ // `kill -l` lists signal names; `kill -0 PID` only probes whether a process
57
+ // exists. Neither delivers a terminating signal.
58
+ kill: /^kill\s+(-l|-0)\b/,
59
+ git: /^git\s+reset\s+(--\s*)?$/
60
+ };
61
+
62
+ /**
63
+ * True when a segment is a read-only use of an otherwise dangerous command.
64
+ *
65
+ * @param {string} segment One shell segment, already quote-masked.
66
+ * @returns {boolean}
67
+ */
68
+ function isReadOnlyInvocation(segment) {
69
+ const text = String(segment || '').trim();
70
+ const leadingWord = text.split(/\s+/)[0];
71
+ const pattern = READ_ONLY_INVOCATIONS[leadingWord];
72
+ return Boolean(pattern && pattern.test(text));
73
+ }
74
+
75
+ /**
76
+ * Does this command line hand a string to something that will execute it?
77
+ *
78
+ * Quote masking is right for `echo "rm -rf /"` and wrong for
79
+ * `bash -c "rm -rf ~"` — in the second the quoted text IS the payload. When an
80
+ * interpreter is present we judge the raw line instead, so masking can never
81
+ * downgrade a real destructive command.
82
+ */
83
+ // Includes the scripting interpreters (perl/ruby/awk/python/node/…) so that a
84
+ // `python3 -c "import os; os.system('rm -rf ~')"` or `perl -e '...'` is judged on
85
+ // the RAW line: the destructive payload lives in the quoted argument, and without
86
+ // these an attacker could hide any command inside an interpreter string and have
87
+ // it read as merely 'moderate' (auto-run in Mode C). Matching only forces the raw
88
+ // line to be scanned — a benign `python3 script.py` still classifies as before.
89
+ const INTERPRETER_INVOCATION = /(^|\s)(sh|bash|zsh|ksh|dash|fish|eval|xargs|ssh|sudo|env|nohup|command|perl|ruby|awk|gawk|mawk|tclsh|php|lua|luajit|python3|python|pwsh|powershell|node|deno|bun|rscript)(\s|$)/i;
90
+
91
+ /**
92
+ * Files where a single appended line changes what the machine does next, or who
93
+ * can log into it. Written out plainly and unencoded — this is Vision section 10
94
+ * defence code and must stay auditable.
95
+ */
96
+ /**
97
+ * Credential stores and key material.
98
+ *
99
+ * L4 (local-path audit): the DEFAULT workspace scope ends with the home
100
+ * directory itself as a fallback, which silently puts ~/.ssh, ~/.aws and
101
+ * ~/.causyne (the encrypted API key AND the .vault_key that decrypts it) inside
102
+ * the agent's reach with no prompt. Vision section 4 asks for freedom WITH a
103
+ * human in the loop rather than a wall, so these are not blocked — touching one
104
+ * is classified 'extreme', which always requires explicit approval in every
105
+ * autonomy mode, including Mode C.
106
+ */
107
+ const CREDENTIAL_PATHS = [
108
+ /(^|\/)\.ssh(\/|$)/,
109
+ /(^|\/)\.aws(\/|$)/,
110
+ /(^|\/)\.gnupg(\/|$)/,
111
+ /(^|\/)\.docker\/config\.json$/,
112
+ /(^|\/)\.netrc$/,
113
+ /(^|\/)\.npmrc$/,
114
+ /(^|\/)\.kube(\/|$)/,
115
+ /(^|\/)Library\/Keychains(\/|$)/i,
116
+ // Causyne's own vault: config.json holds the encrypted provider key and
117
+ // .vault_key is the key that decrypts it.
118
+ /(^|\/)\.causyne(\/|$)/
119
+ ];
120
+
121
+ export function isCredentialPath(targetPath) {
122
+ if (typeof targetPath !== 'string' || targetPath.length === 0) return false;
123
+ const normalized = targetPath.replace(/\\/g, '/');
124
+ return CREDENTIAL_PATHS.some(pattern => pattern.test(normalized));
125
+ }
126
+
127
+ const SENSITIVE_WRITE_TARGETS = [
128
+ // Shell startup files: an appended line runs on the user's next shell.
129
+ /(^|\/)\.(bashrc|bash_profile|bash_login|profile|zshrc|zprofile|zshenv|zlogin)$/,
130
+ /(^|\/)\.config\/fish\/config\.fish$/,
131
+ // SSH: authorized_keys grants login; config can silently reroute connections.
132
+ /(^|\/)\.ssh\//,
133
+ // Scheduled execution.
134
+ /(^|\/)(crontab|cron\.d)(\/|$)/,
135
+ /\/etc\//,
136
+ // Causyne's own state: config.json holds the encrypted API key and .vault_key
137
+ // is the key that decrypts it.
138
+ /(^|\/)\.causyne\//
139
+ ];
140
+
141
+ /**
142
+ * Extract the file targets a command line redirects into.
143
+ *
144
+ * Context-aware per rule ZM-09: `2>&1` and `>&2` are descriptor duplications,
145
+ * not files, and must not be reported as write targets — a guard that fires on
146
+ * those would be disabled within a day.
147
+ */
148
+ function redirectionTargets(maskedCommand) {
149
+ const targets = [];
150
+ const pattern = /(?:^|[^0-9&>])>>?\s*([^\s;|&<>]+)/g;
151
+ let match;
152
+ while ((match = pattern.exec(maskedCommand)) !== null) {
153
+ const target = match[1];
154
+ if (!target || target.startsWith('&')) continue;
155
+ targets.push(target);
156
+ }
157
+ return targets;
158
+ }
159
+
160
+ function redirectsIntoSensitiveTarget(maskedCommand) {
161
+ for (const target of redirectionTargets(maskedCommand)) {
162
+ const normalizedTarget = target.replace(/^~/, '/home/user');
163
+ for (const pattern of SENSITIVE_WRITE_TARGETS) {
164
+ if (pattern.test(normalizedTarget)) return true;
165
+ }
166
+ }
167
+ return false;
168
+ }
169
+
170
+ export class Sandbox {
171
+ constructor() {
172
+ this.config = loadConfig();
173
+
174
+ // V5 Upgrade: Expanded default workspace paths
175
+ // Instead of only os.homedir() (which was too restrictive and caused path errors),
176
+ // we now default to common user directories while still blocking system dirs.
177
+ const home = os.homedir();
178
+ this.workspacePaths = this.config.workspacePaths || [
179
+ path.join(home, 'Desktop'),
180
+ path.join(home, 'Documents'),
181
+ path.join(home, 'Downloads'),
182
+ path.join(home, 'Projects'),
183
+ path.join(home, 'CausyneWorkspace'),
184
+ path.join(home, 'Causyne'),
185
+ home // Fallback: allow home dir itself (covers edge cases like ~/scripts, ~/.config)
186
+ ];
187
+
188
+ // Developer emulator mode (hardened, secret-gated — see devMode.js) grants
189
+ // full local access for agent development on a Fast-tier emulator. It cannot
190
+ // be triggered by a bare env flag in the published package.
191
+ const devMode = isDevEmulator(this.config);
192
+ this.grantAllAccess = devMode ? true : (this.config.grantAllAccess || false);
193
+ this.allowedTools = devMode ? ['*'] : (this.config.allowedTools || ['*']);
194
+ }
195
+
196
+
197
+ updateWorkspacePaths(newPaths) {
198
+ if (Array.isArray(newPaths) && newPaths.length > 0) {
199
+ // Validate: reject any system-critical paths
200
+ const BLOCKED_ROOTS = ['/etc', '/usr', '/bin', '/sbin', '/System', '/var', '/private', '/Library'];
201
+ const safePaths = newPaths.filter(p => {
202
+ const resolved = path.resolve(p);
203
+ return !BLOCKED_ROOTS.some(blocked => resolved.startsWith(blocked));
204
+ });
205
+
206
+ if (safePaths.length > 0) {
207
+ this.workspacePaths = safePaths;
208
+ return { success: true, paths: this.workspacePaths };
209
+ }
210
+ }
211
+ return { success: false, reason: 'Invalid or blocked paths provided' };
212
+ }
213
+
214
+ // V5: Add a single workspace path
215
+ addWorkspacePath(newPath) {
216
+ const resolved = path.resolve(newPath);
217
+ if (!this.workspacePaths.includes(resolved)) {
218
+ this.workspacePaths.push(resolved);
219
+ }
220
+ return this.workspacePaths;
221
+ }
222
+
223
+ validate(toolName, args) {
224
+ // 1. Tool availability check
225
+ if (!this.allowedTools.includes('*') && !this.allowedTools.includes(toolName)) {
226
+ return { allowed: false, reason: `Tool ${toolName} not allowed on current tier`, risk: 'forbidden' };
227
+ }
228
+
229
+ // 2. Path scoping.
230
+ // Tools do not agree on what to call their path argument: MultiEdit uses
231
+ // file_path, the Blender/Godot automators use filepath and directory, the
232
+ // media tools use outputPath. Checking only `path` let all of those write
233
+ // outside the workspace unchallenged.
234
+ const PATH_ARGUMENTS = ['path', 'cwd', 'source', 'destination', 'file_path', 'filepath', 'directory', 'targetFile', 'outputFile', 'outputPath'];
235
+ const pathsToCheck = [];
236
+ for (const field of PATH_ARGUMENTS) {
237
+ if (args[field]) pathsToCheck.push(args[field]);
238
+ }
239
+
240
+ for (const p of pathsToCheck) {
241
+ if (typeof p === 'string' && !this.isPathAllowed(p)) {
242
+ return { allowed: false, reason: `Path outside allowed workspace: ${p}`, risk: 'dangerous' };
243
+ }
244
+ }
245
+
246
+ // L4: a credential store is in reach by default (the home fallback), so it
247
+ // gets a human in the loop rather than a wall. 'extreme' is the one risk
248
+ // level Mode C cannot auto-approve.
249
+ for (const p of pathsToCheck) {
250
+ if (typeof p === 'string' && isCredentialPath(p)) {
251
+ return { allowed: true, risk: 'extreme', reason: `Touches credential material: ${p}` };
252
+ }
253
+ }
254
+
255
+ // 3. Command classification (for terminal tools).
256
+ // terminal_persistent_shell runs arbitrary commands exactly like sysRun does
257
+ // and must go through the same classifier — it previously bypassed it.
258
+ if (toolName === 'sysRun' || toolName === 'sysRunBg' || toolName === 'terminal_persistent_shell') {
259
+ const command = args.command || args.input;
260
+ if (command) {
261
+ const risk = this.classifyCommandRisk(command);
262
+ if (risk === 'forbidden') {
263
+ return { allowed: false, reason: 'Command is permanently blocked for safety', risk: 'forbidden' };
264
+ }
265
+ return { allowed: true, risk };
266
+ }
267
+ return { allowed: true, risk: 'moderate' };
268
+ }
269
+
270
+ // 4. SSRF protection (for web fetch)
271
+ if (toolName === 'web_fetch' && args.url) {
272
+ if (this.isInternalUrl(args.url)) {
273
+ return { allowed: false, reason: 'Cannot access internal/local network URLs (SSRF Protection)', risk: 'forbidden' };
274
+ }
275
+ if (args.outputFile) {
276
+ return { allowed: true, risk: 'extreme', reason: 'Downloads network content to disk' };
277
+ }
278
+ }
279
+
280
+ if (toolName === 'game_asset_fetch' && args.action === 'download_asset') {
281
+ if (!args.url || this.isInternalUrl(args.url)) {
282
+ return { allowed: false, reason: 'Cannot download from an internal or invalid URL', risk: 'forbidden' };
283
+ }
284
+ return { allowed: true, risk: 'extreme', reason: 'Downloads a third-party asset to disk' };
285
+ }
286
+
287
+ // Heavy AI downloads ALWAYS require consent (even in Mode C)
288
+ if (toolName === 'manage_local_dependencies') {
289
+ // grant_capability installs third-party packages into the user's home
290
+ // directory, so it belongs in exactly the same consent class as the other
291
+ // download actions. Routing it here is what keeps ZM-01 true: nothing is
292
+ // ever installed without an explicit human approval.
293
+ const heavyActions = ['pipInst', 'grant_capability', 'grant_mcp_server'];
294
+ if (heavyActions.includes(args.action)) {
295
+ return { allowed: true, risk: 'extreme' }; // Upgraded from dangerous to extreme for safe C Mode handling
296
+ }
297
+ return { allowed: true, risk: 'moderate' };
298
+ }
299
+
300
+ // Tools that overwrite or delete are inherently dangerous.
301
+ // These comparisons used the dotted schema names (`fs.delete`, `proc.term`,
302
+ // `fs.write`) while validate() is always called with the canonical wire name
303
+ // (`fs_delete`, ...), so none of them ever matched: the Human-in-the-Loop
304
+ // approval gate for destructive operations had never once fired.
305
+ if (toolName === 'fs_delete') {
306
+ // If wiping an entire directory tree, flag as extreme
307
+ if (args.path && !args.path.includes('.')) {
308
+ return { allowed: true, risk: 'extreme' };
309
+ }
310
+ return { allowed: true, risk: 'dangerous' };
311
+ }
312
+ if (toolName === 'proc_term') {
313
+ return { allowed: true, risk: 'dangerous' };
314
+ }
315
+ // Driving the mouse and keyboard reaches every application the user is
316
+ // signed into — mail, banking, anything with a session open. There is no
317
+ // safe subset and no autonomy mode in which it should run unannounced, so
318
+
319
+ // Mode C. Section 4 says gated, not walled — the capability is real, the
320
+ // human just says yes first.
321
+ if (toolName === 'desktop_act') {
322
+ return { allowed: true, risk: 'extreme' };
323
+ }
324
+ if (toolName === 'fs_write' || toolName === 'fs_move' || toolName === 'fs_edit' || toolName === 'MultiEdit') {
325
+ return { allowed: true, risk: 'moderate' };
326
+ }
327
+
328
+ return { allowed: true, risk: 'safe' };
329
+ }
330
+
331
+ isPathAllowed(targetPath) {
332
+ // Expand ~
333
+ let resolved = targetPath;
334
+ if (resolved.startsWith('~/')) {
335
+ resolved = path.join(os.homedir(), resolved.slice(2));
336
+ }
337
+ resolved = path.resolve(resolved);
338
+
339
+ // V5: Block system-critical paths BEFORE checking allowed paths (OS Protection)
340
+ const isWindows = os.platform() === 'win32';
341
+ let isBlocked = false;
342
+
343
+ if (isWindows) {
344
+ const WIN_BLOCKED = ['C:\\Windows', 'C:\\Program Files', 'C:\\ProgramData'];
345
+ isBlocked = WIN_BLOCKED.some(blocked => resolved.toLowerCase().startsWith(blocked.toLowerCase()));
346
+ } else {
347
+ const UNIX_BLOCKED = ['/etc', '/usr/bin', '/usr/sbin', '/sbin', '/bin', '/System', '/private/var'];
348
+ isBlocked = UNIX_BLOCKED.some(blocked => resolved.startsWith(blocked));
349
+ }
350
+
351
+ if (isBlocked) {
352
+ return false;
353
+ }
354
+
355
+ // V7 Ultimate Self-Preservation: Block access to the agent's own source code
356
+ // The AGI cannot read, modify, or delete causyne-agent itself.
357
+ const agentBinOrScript = process.argv[1] || '';
358
+ let agentRoot = '';
359
+ if (agentBinOrScript && agentBinOrScript.includes('causyne-agent')) {
360
+ const idx = agentBinOrScript.lastIndexOf('causyne-agent');
361
+ agentRoot = path.resolve(agentBinOrScript.slice(0, idx + 'causyne-agent'.length));
362
+ } else if (typeof __dirname !== 'undefined') {
363
+ agentRoot = path.resolve(__dirname, '..');
364
+ }
365
+ if (agentRoot && resolved.startsWith(agentRoot) && resolved !== path.resolve(os.homedir())) {
366
+ return false;
367
+ }
368
+
369
+ // V6: Override workspace limits if grantAllAccess is active
370
+ if (this.grantAllAccess) {
371
+ return true;
372
+ }
373
+
374
+ // Check if resolved path is inside any allowed workspace path.
375
+ //
376
+ // L2 (local-path audit): this was a bare `resolved.startsWith(allowedBase)`,
377
+ // which is a STRING prefix test, not a PATH containment test. With the scope
378
+ // set to /Users/x/Projects it also accepted /Users/x/Projects-secret and
379
+ // /Users/x/ProjectsBACKUP — any sibling whose name merely begins with the
380
+ // workspace name. The separator check is what makes it a directory boundary.
381
+ return this.workspacePaths.some(workspace => {
382
+ let allowedBase = workspace;
383
+ if (allowedBase.startsWith('~/')) {
384
+ allowedBase = path.join(os.homedir(), allowedBase.slice(2));
385
+ }
386
+ allowedBase = path.resolve(allowedBase);
387
+ return resolved === allowedBase || resolved.startsWith(allowedBase + path.sep);
388
+ });
389
+ }
390
+
391
+ isInternalUrl(urlString) {
392
+ try {
393
+ const url = new URL(urlString);
394
+ const host = url.hostname;
395
+
396
+ // Block localhost, private IPs, cloud metadata
397
+ const blockedRegex = /^(localhost|127\.\d+\.\d+\.\d+|10\.\d+\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+|169\.254\.\d+\.\d+|0\.0\.0\.0)$/;
398
+ if (blockedRegex.test(host)) return true;
399
+
400
+ return false;
401
+ } catch(e) {
402
+ return true; // If URL is unparseable, block it to be safe
403
+ }
404
+ }
405
+
406
+ classifyCommandRisk(command) {
407
+ if (!command) return 'safe';
408
+ const normalized = command.trim().toLowerCase();
409
+
410
+ // FORBIDDEN — absolute blocklist (Technical + Ethical Safety Net Layer 3)
411
+ const FORBIDDEN_PATTERNS = [
412
+ /:\(\)\s*\{\s*:\|:&\s*\};/,
413
+ /curl\s+.*\|\s*(sh|bash)/,
414
+ /wget\s+.*\|\s*(sh|bash)/,
415
+ // Process substitution — `bash <(curl …)` / `sh <(wget …)` fetch-and-run,
416
+ // the pipe-to-shell trick's twin that the `| sh` patterns above miss.
417
+ /<\(\s*(curl|wget)\b/,
418
+ />\s*\/dev\/sda/,
419
+ // Ethical Safety Gate: Offensive security attack tools & ransomware patterns
420
+ /nmap\s+-s[STUVFN]/i,
421
+ /sqlmap/i,
422
+ /metasploit|msfconsole|msfvenom/i,
423
+ /hydra\s+/i,
424
+ /john\s+/i,
425
+ /hashcat/i,
426
+ /aircrack|airmon|aireplay/i,
427
+ /ettercap|arpspoof|bettercap/i,
428
+ /nikto/i,
429
+ /xmrig|cpuminer|minerd/i,
430
+ /ransomware|encrypt.*ransom/i
431
+ ];
432
+
433
+ // FORBIDDEN is matched against the WHOLE line, never against segments:
434
+ // several of these patterns deliberately span shell separators (the fork
435
+ // bomb contains ; and |, the pipe-to-shell patterns contain |), so
436
+ // splitting first would stop them matching at all.
437
+ for (const pattern of FORBIDDEN_PATTERNS) {
438
+ if (pattern.test(normalized)) return 'forbidden';
439
+ }
440
+
441
+ // Everything below judges what the shell will actually execute.
442
+ const masked = maskQuotedSpans(normalized);
443
+
444
+
445
+ // L3 (local-path audit): classify the REDIRECTION TARGET, not just the verb.
446
+ //
447
+ // sandbox.validate() path-scopes tool ARGUMENTS, but a shell redirection
448
+ // target is invisible to it, and the classifier only ever looked at the
449
+ // command word. So `echo evil >> ~/.bashrc` and
450
+ // `cat payload >> ~/.ssh/authorized_keys` both classified as SAFE and
451
+ // auto-executed in every autonomy mode — persistence and remote access with
452
+ // no approval prompt. Writing to these files is escalated regardless of
453
+ // which harmless-looking command does the writing.
454
+ if (redirectsIntoSensitiveTarget(masked)) return 'extreme';
455
+ const executesQuotedText = INTERPRETER_INVOCATION.test(masked);
456
+ const destructiveScanTarget = executesQuotedText ? normalized : masked;
457
+
458
+
459
+ const EXTREME_PATTERNS = [
460
+ /rm\s+(-rf|-fr)\s+[\/~]/,
461
+ /rm\s+(-rf|-fr)\s+\*/,
462
+ /mkfs/,
463
+ /dd\s+if=/,
464
+ /shutdown|reboot|init\s+[06]/,
465
+ /passwd|useradd|userdel/,
466
+ /iptables|ufw\s+delete/,
467
+ /format\s+[a-z]:/i,
468
+ /diskpart/i
469
+ ];
470
+
471
+ for (const pattern of EXTREME_PATTERNS) {
472
+ if (pattern.test(destructiveScanTarget)) return 'extreme';
473
+ }
474
+
475
+ // DANGEROUS — requires approval in Mode B, auto-executes in Mode C
476
+ const DANGEROUS_PREFIXES = [
477
+ 'rm ', 'sudo ', 'su ', 'chmod ', 'chown ',
478
+ 'apt ', 'apt-get ', 'brew ', 'yum ', 'dnf ',
479
+ 'git reset', 'git push --force', 'git push -f', 'git rebase',
480
+ 'kill ', 'pkill ', 'killall ',
481
+ 'ssh ', 'scp ', 'rsync ',
482
+ 'mv ', 'crontab ', 'systemctl ', 'service '
483
+ ];
484
+
485
+ // Prefix rules are per-segment: `cd x && rm -f y` must be judged on `rm`,
486
+ // not on `cd`. Highest risk across all segments wins.
487
+ const segments = splitShellSegments(masked);
488
+
489
+ for (const segment of segments) {
490
+ for (const prefix of DANGEROUS_PREFIXES) {
491
+ if (segment.startsWith(prefix) && !isReadOnlyInvocation(segment)) return 'dangerous';
492
+ }
493
+ }
494
+
495
+ // DANGEROUS — pattern form, for commands whose risk is in a flag rather than
496
+ // the leading word. `find … -exec/-delete` runs an arbitrary command against
497
+ // (or removes) every match, so it is at least as risky as a bare `rm`.
498
+ const DANGEROUS_PATTERNS = [
499
+ /\bfind\b[\s\S]*\s-(exec|execdir|delete)\b/,
500
+ ];
501
+ for (const pattern of DANGEROUS_PATTERNS) {
502
+ if (pattern.test(destructiveScanTarget)) return 'dangerous';
503
+ }
504
+
505
+ // MODERATE — auto-execute + notify in Mode B
506
+ const MODERATE_PREFIXES = [
507
+ 'npm i', 'npm run', 'npx ', 'yarn ', 'pnpm ',
508
+ 'pip install', 'pip uninstall', 'pip3 ',
509
+ 'python', 'python3', 'node ', 'deno ', 'bun ',
510
+ 'perl ', 'ruby ', 'awk ', 'gawk ', 'php ', 'lua ', 'tclsh ', 'rscript ',
511
+ 'pwsh ', 'powershell ',
512
+ 'git add', 'git commit', 'git push', 'git pull', 'git checkout',
513
+ 'git merge', 'git branch', 'git tag',
514
+ 'mkdir', 'touch', 'cp ',
515
+ 'cargo ', 'go ', 'make', 'cmake', 'gcc', 'g++',
516
+ 'docker ', 'docker-compose ', 'podman ',
517
+ 'code ', 'open ', 'xdg-open ',
518
+ 'curl ', 'wget '
519
+ ];
520
+
521
+ for (const segment of segments) {
522
+ for (const prefix of MODERATE_PREFIXES) {
523
+ if (segment.startsWith(prefix)) return 'moderate';
524
+ }
525
+ }
526
+
527
+ return 'safe';
528
+ }
529
+ }