@getmarrow/mcp 3.9.102 → 3.9.106

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 (152) hide show
  1. package/README.md +71 -24
  2. package/dist/approval-reply.d.ts +2 -0
  3. package/dist/approval-reply.d.ts.map +1 -0
  4. package/dist/approval-reply.js +18 -0
  5. package/dist/approval-reply.js.map +1 -0
  6. package/dist/cli.js +1716 -1644
  7. package/dist/cli.js.map +1 -1
  8. package/dist/codex-plugin-pins.d.ts +19 -0
  9. package/dist/codex-plugin-pins.d.ts.map +1 -0
  10. package/dist/codex-plugin-pins.js +61 -0
  11. package/dist/codex-plugin-pins.js.map +1 -0
  12. package/dist/codex-plugins.d.ts +34 -0
  13. package/dist/codex-plugins.d.ts.map +1 -0
  14. package/dist/codex-plugins.js +240 -0
  15. package/dist/codex-plugins.js.map +1 -0
  16. package/dist/env.d.ts +8 -0
  17. package/dist/env.d.ts.map +1 -1
  18. package/dist/env.js +83 -4
  19. package/dist/env.js.map +1 -1
  20. package/dist/hook-context.d.ts.map +1 -1
  21. package/dist/hook-context.js +25 -0
  22. package/dist/hook-context.js.map +1 -1
  23. package/dist/hook-contract.d.ts +4 -3
  24. package/dist/hook-contract.d.ts.map +1 -1
  25. package/dist/hook-contract.js +43 -5
  26. package/dist/hook-contract.js.map +1 -1
  27. package/dist/hook-pre-action.d.ts +80 -3
  28. package/dist/hook-pre-action.d.ts.map +1 -1
  29. package/dist/hook-pre-action.js +435 -29
  30. package/dist/hook-pre-action.js.map +1 -1
  31. package/dist/hook-tool-policy.d.ts +73 -4
  32. package/dist/hook-tool-policy.d.ts.map +1 -1
  33. package/dist/hook-tool-policy.js +287 -18
  34. package/dist/hook-tool-policy.js.map +1 -1
  35. package/dist/hook.d.ts +1 -1
  36. package/dist/hook.d.ts.map +1 -1
  37. package/dist/hook.js +15 -7
  38. package/dist/hook.js.map +1 -1
  39. package/dist/host-approval-state.d.ts +7 -0
  40. package/dist/host-approval-state.d.ts.map +1 -1
  41. package/dist/host-approval-state.js +15 -0
  42. package/dist/host-approval-state.js.map +1 -1
  43. package/dist/host-approval.d.ts +22 -1
  44. package/dist/host-approval.d.ts.map +1 -1
  45. package/dist/host-approval.js +65 -4
  46. package/dist/host-approval.js.map +1 -1
  47. package/dist/host-session.d.ts +11 -0
  48. package/dist/host-session.d.ts.map +1 -1
  49. package/dist/host-session.js +49 -0
  50. package/dist/host-session.js.map +1 -1
  51. package/dist/index.d.ts +5 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +23 -11
  54. package/dist/index.js.map +1 -1
  55. package/dist/keyless-server.d.ts +53 -0
  56. package/dist/keyless-server.d.ts.map +1 -0
  57. package/dist/keyless-server.js +230 -0
  58. package/dist/keyless-server.js.map +1 -0
  59. package/dist/normalized-action.d.ts +31 -6
  60. package/dist/normalized-action.d.ts.map +1 -1
  61. package/dist/normalized-action.js +550 -105
  62. package/dist/normalized-action.js.map +1 -1
  63. package/dist/own-server-identity.d.ts +142 -0
  64. package/dist/own-server-identity.d.ts.map +1 -0
  65. package/dist/own-server-identity.js +1123 -0
  66. package/dist/own-server-identity.js.map +1 -0
  67. package/dist/own-tool.d.ts +21 -9
  68. package/dist/own-tool.d.ts.map +1 -1
  69. package/dist/own-tool.js +25 -10
  70. package/dist/own-tool.js.map +1 -1
  71. package/dist/redact.d.ts +23 -0
  72. package/dist/redact.d.ts.map +1 -1
  73. package/dist/redact.js +181 -8
  74. package/dist/redact.js.map +1 -1
  75. package/dist/seatbelt/classifier-pin.d.ts +15 -0
  76. package/dist/seatbelt/classifier-pin.d.ts.map +1 -0
  77. package/dist/seatbelt/classifier-pin.js +26 -0
  78. package/dist/seatbelt/classifier-pin.js.map +1 -0
  79. package/dist/seatbelt/env-dump.d.ts +45 -0
  80. package/dist/seatbelt/env-dump.d.ts.map +1 -0
  81. package/dist/seatbelt/env-dump.js +308 -0
  82. package/dist/seatbelt/env-dump.js.map +1 -0
  83. package/dist/seatbelt/fallback-copy.d.ts +16 -0
  84. package/dist/seatbelt/fallback-copy.d.ts.map +1 -0
  85. package/dist/seatbelt/fallback-copy.js +68 -0
  86. package/dist/seatbelt/fallback-copy.js.map +1 -0
  87. package/dist/seatbelt/guards.d.ts +10 -0
  88. package/dist/seatbelt/guards.d.ts.map +1 -0
  89. package/dist/seatbelt/guards.js +509 -0
  90. package/dist/seatbelt/guards.js.map +1 -0
  91. package/dist/seatbelt/hook.d.ts +91 -0
  92. package/dist/seatbelt/hook.d.ts.map +1 -0
  93. package/dist/seatbelt/hook.js +396 -0
  94. package/dist/seatbelt/hook.js.map +1 -0
  95. package/dist/seatbelt/longopts.d.ts +68 -0
  96. package/dist/seatbelt/longopts.d.ts.map +1 -0
  97. package/dist/seatbelt/longopts.js +377 -0
  98. package/dist/seatbelt/longopts.js.map +1 -0
  99. package/dist/seatbelt/mapper.d.ts +83 -0
  100. package/dist/seatbelt/mapper.d.ts.map +1 -0
  101. package/dist/seatbelt/mapper.js +10581 -0
  102. package/dist/seatbelt/mapper.js.map +1 -0
  103. package/dist/seatbelt/record.d.ts +94 -0
  104. package/dist/seatbelt/record.d.ts.map +1 -0
  105. package/dist/seatbelt/record.js +776 -0
  106. package/dist/seatbelt/record.js.map +1 -0
  107. package/dist/seatbelt/tables.d.ts +82 -0
  108. package/dist/seatbelt/tables.d.ts.map +1 -0
  109. package/dist/seatbelt/tables.js +352 -0
  110. package/dist/seatbelt/tables.js.map +1 -0
  111. package/dist/seatbelt-deploy.d.ts +100 -0
  112. package/dist/seatbelt-deploy.d.ts.map +1 -0
  113. package/dist/seatbelt-deploy.js +393 -0
  114. package/dist/seatbelt-deploy.js.map +1 -0
  115. package/dist/seatbelt-fs.d.ts +30 -0
  116. package/dist/seatbelt-fs.d.ts.map +1 -0
  117. package/dist/seatbelt-fs.js +94 -0
  118. package/dist/seatbelt-fs.js.map +1 -0
  119. package/dist/seatbelt-git.d.ts +212 -0
  120. package/dist/seatbelt-git.d.ts.map +1 -0
  121. package/dist/seatbelt-git.js +844 -0
  122. package/dist/seatbelt-git.js.map +1 -0
  123. package/dist/seatbelt-mode.d.ts +77 -0
  124. package/dist/seatbelt-mode.d.ts.map +1 -0
  125. package/dist/seatbelt-mode.js +496 -0
  126. package/dist/seatbelt-mode.js.map +1 -0
  127. package/dist/seatbelt-path.d.ts +123 -0
  128. package/dist/seatbelt-path.d.ts.map +1 -0
  129. package/dist/seatbelt-path.js +482 -0
  130. package/dist/seatbelt-path.js.map +1 -0
  131. package/dist/seatbelt-scripts.d.ts +109 -0
  132. package/dist/seatbelt-scripts.d.ts.map +1 -0
  133. package/dist/seatbelt-scripts.js +1060 -0
  134. package/dist/seatbelt-scripts.js.map +1 -0
  135. package/dist/secret-paths.d.ts +17 -0
  136. package/dist/secret-paths.d.ts.map +1 -0
  137. package/dist/secret-paths.js +32 -0
  138. package/dist/secret-paths.js.map +1 -0
  139. package/dist/session-loop-guard.d.ts.map +1 -1
  140. package/dist/session-loop-guard.js +15 -5
  141. package/dist/session-loop-guard.js.map +1 -1
  142. package/dist/tool-catalog.js +2 -2
  143. package/dist/tool-catalog.js.map +1 -1
  144. package/dist/types.d.ts +2 -1
  145. package/dist/types.d.ts.map +1 -1
  146. package/package.json +3 -2
  147. package/server.json +3 -3
  148. package/vendor/classifier/MANIFEST.json +25 -0
  149. package/vendor/classifier/classifier.js +1356 -0
  150. package/vendor/classifier/policy-baseline.js +187 -0
  151. package/vendor/classifier/redact.js +59 -0
  152. package/vendor/classifier/shell-parse.js +455 -0
@@ -0,0 +1,187 @@
1
+ 'use strict';
2
+
3
+ // Built-in baseline policy (contract marrow.classifier.v1). It is the same data shape the server
4
+ // signs and ships in a policy bundle; a verified bundle replaces it. The baseline is deliberately
5
+ // conservative: it only names work that is safe to allow locally without a server round trip.
6
+ // Everything it does not name is "unknown" and goes to the server gate.
7
+ //
8
+ // Program rule forms:
9
+ // 'routine' | 'unknown' | 'risky' fixed class for any arguments
10
+ // { read: true } routine unless an argument is a secret path
11
+ // { write: true } path arguments are write targets (protected -> risky)
12
+ // { sub: {...}, default, skip } first non-option argument selects the class
13
+ // { handler: '<name>' } special handling in classifier.js
14
+
15
+ const SUB_FLAGS_WITH_VALUE = {
16
+ git: ['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--exec-path'],
17
+ docker: ['-H', '--host', '--context', '-c', '--config', '-l', '--log-level'],
18
+ kubectl: ['-n', '--namespace', '--context', '--cluster', '--kubeconfig', '-s', '--server', '--user'],
19
+ npm: ['--prefix', '-C', '-w', '--workspace'],
20
+ pnpm: ['-C', '--dir', '--filter', '-F', '-w'],
21
+ yarn: ['--cwd'],
22
+ gh: ['-R', '--repo'],
23
+ cargo: ['--manifest-path', '-Z', '--config'],
24
+ terraform: ['-chdir'],
25
+ };
26
+
27
+ const BASELINE_POLICY = Object.freeze({
28
+ contract: 'marrow.classifier.v1',
29
+ version: 0,
30
+ source: 'builtin_baseline',
31
+
32
+ tools: {
33
+ read_only: [
34
+ 'read', 'grep', 'glob', 'ls', 'websearch', 'web_search', 'todowrite', 'todoread', 'task', 'agent',
35
+ 'exitplanmode', 'notebookread', 'bashoutput', 'killshell', 'askuserquestion', 'skill', 'toolsearch',
36
+ 'slashcommand', 'list_dir', 'read_file', 'grep_files', 'view_image', 'update_plan', 'listmcpresourcestool',
37
+ 'readmcpresourcetool', 'taskoutput', 'taskstop', 'enterplanmode', 'monitor',
38
+ ],
39
+ edit: ['write', 'edit', 'multiedit', 'notebookedit', 'apply_patch', 'write_file', 'create_file', 'str_replace_editor', 'str_replace_based_edit_tool'],
40
+ shell: ['bash', 'shell', 'local_shell', 'exec_command', 'container.exec', 'run_terminal_command', 'execute_command', 'run_shell_command'],
41
+ fetch: ['webfetch', 'web_fetch', 'fetch'],
42
+ // Marrow's own MCP tools are governed server-side; the exact official namespace only.
43
+ marrow_mcp_prefix: 'mcp__marrow__marrow_',
44
+ },
45
+
46
+ // A mutation verb anywhere in an unknown or MCP tool name makes it risky; read verbs never
47
+ // override it (ADV-06: fetch_prod_secrets, query_and_drop_table, status_wipe).
48
+ mutation_verbs: [
49
+ 'delete', 'drop', 'wipe', 'purge', 'destroy', 'truncate', 'terminate', 'grant', 'revoke', 'disable', 'flush',
50
+ 'deploy', 'publish', 'release', 'merge', 'refund', 'charge', 'payout', 'transfer', 'pay', 'rotate', 'secret',
51
+ 'secrets', 'credential', 'credentials', 'token', 'password', 'rollback', 'shutdown', 'kill', 'reset', 'remove',
52
+ 'rm', 'overwrite', 'force', 'prod', 'production', 'send', 'email', 'invite', 'approve', 'archive', 'unpublish',
53
+ ],
54
+
55
+ protected_branches: ['main', 'master', 'production', 'prod', 'release', 'release/*', 'releases/*', 'live', 'stable', 'trunk', 'hotfix/*', 'deploy', 'deploy/*', 'gh-pages'],
56
+
57
+ // Writing these is risky: they disable or redirect Marrow, a harness's hooks, or the shell.
58
+ protected_paths: [
59
+ '~/.marrow/**', '**/.marrow/**',
60
+ '~/.claude.json', '~/.claude/settings.json', '~/.claude/settings.local.json', '**/.claude/settings.json', '**/.claude/settings.local.json',
61
+ '**/.mcp.json', '~/.cursor/**', '**/.cursor/mcp.json', '**/.cursor/hooks.json', '~/.codex/**', '**/.codex/**',
62
+ '~/.gemini/**', '**/.gemini/settings.json', '**/.windsurf/**', '~/.codeium/**', '**/.clinerules/hooks/**',
63
+ '~/.grok/**', '**/.grok/**', '~/.hermes/**', '~/.openclaw/**',
64
+ '~/.config/systemd/user/**', '~/Library/LaunchAgents/**', '**/.config/environment.d/**', '**/.pam_environment',
65
+ // Directories on PATH: an executable written here shadows a routine program later.
66
+ '~/.local/bin/**', '~/bin/**', '/usr/local/bin/**', '/opt/homebrew/bin/**', '**/node_modules/.bin/**',
67
+ '**/.bashrc', '**/.bash_profile', '**/.bash_login', '**/.profile', '**/.zshrc', '**/.zshenv', '**/.zprofile', '**/.config/fish/**',
68
+ '**/.claude.json', '**/.config/systemd/user/**',
69
+ '/etc/**', '/usr/**', '/bin/**', '/sbin/**', '/boot/**', '/lib/**', '/var/lib/**', '/System/**',
70
+ '**/.git/hooks/**', '**/.git/config',
71
+ '**/.npmrc', '**/.gitconfig', '**/.git-credentials', '**/.ssh/**', '**/.aws/**', '**/.kube/**', '**/.docker/config.json',
72
+ ],
73
+ // Reading these is risky (credential exposure).
74
+ secret_paths: [
75
+ // Home-agnostic on purpose: another user's or root's credential files are just as secret.
76
+ '**/.env', '**/.env.*', '**/.aws/**', '**/.ssh/**', '**/.npmrc', '**/.git-credentials', '**/.netrc', '**/.config/gh/**',
77
+ '**/.docker/config.json', '**/.kube/config', '**/.marrow/env', '**/.marrow/env.local', '**/.claude.json', '**/*.pem', '**/*.key',
78
+ '**/id_rsa', '**/id_rsa.*', '**/id_ed25519', '**/id_ed25519.*', '**/id_ecdsa', '**/credentials.json', '**/.dev.vars',
79
+ '**/.config/gcloud/**', '**/.azure/**', '**/.pypirc', '**/.gem/credentials', '**/.cargo/credentials*', '/etc/shadow',
80
+ '**/.hermes/config.yaml', '**/.openclaw/.env',
81
+ ],
82
+ secret_path_exceptions: ['**/.env.example', '**/.env.sample', '**/.env.template', '**/.env.dist'],
83
+ // Edits here are not blocked locally but go to the server gate.
84
+ review_paths: ['**/.husky/**', '**/.githooks/**', '**/lefthook.yml', '**/.pre-commit-config.yaml', '**/.github/workflows/**', '**/Dockerfile', '**/wrangler.toml', '**/wrangler.json', '**/terraform/**', '**/*.tf', '**/k8s/**', '**/helm/**'],
85
+
86
+ url_mutation_pattern: '(delete|wipe|drop|destroy|purge|truncate|refund|charge|payout|transfer|dispatch|deploy|publish|admin|_method=|confirm=|execute|reset)',
87
+ interpreter_danger_pattern: '(rmtree|rmSync|rmdirSync|unlink|os\\.remove|os\\.system|subprocess|child_process|execSync|spawnSync|exec\\(|system\\(|File\\.delete|FileUtils\\.rm|shutil|\\.aws|\\.ssh|\\.env|credentials|id_rsa|id_ed25519|wrangler|deploy|publish|DROP |TRUNCATE|DELETE FROM|/dev/sd|mkfs|dd if=)',
88
+ sql_mutation_pattern: '\\b(drop|delete|truncate|update|alter|insert|grant|revoke|flushall|flushdb|replace into)\\b',
89
+
90
+ wrappers: ['env', 'timeout', 'nice', 'nohup', 'time', 'command', 'builtin', 'exec', 'stdbuf', 'ionice', 'chrt', 'taskset', 'setsid', 'unbuffer', 'caffeinate', 'xargs', 'watch', 'retry'],
91
+ privilege_wrappers: ['sudo', 'doas', 'su', 'pkexec', 'runas'],
92
+ shells: ['sh', 'bash', 'zsh', 'dash', 'ksh', 'fish', 'ash', 'busybox'],
93
+ interpreters: ['python', 'python2', 'python3', 'node', 'nodejs', 'ruby', 'perl', 'php', 'deno', 'bun', 'lua', 'rscript', 'osascript', 'pwsh', 'powershell', 'tclsh', 'jshell', 'groovy', 'scala', 'swift', 'irb', 'ipython'],
94
+ routine_python_modules: ['pytest', 'unittest', 'mypy', 'black', 'ruff', 'flake8', 'pylint', 'pip', 'venv', 'json.tool', 'doctest', 'compileall', 'py_compile', 'coverage', 'isort'],
95
+ routine_npx: ['tsc', 'eslint', 'prettier', 'vitest', 'jest', 'mocha', 'biome', 'c8', 'nyc', 'typescript', 'markdownlint', 'markdownlint-cli2', 'cspell', 'depcheck', 'knip', 'lint-staged', 'playwright',
96
+ 'husky', 'vite', 'next', 'webpack', 'webpack-cli', 'rollup', 'esbuild', 'tsup', 'parcel', 'astro', 'nuxi', 'storybook'],
97
+ routine_package_scripts_depth: 3,
98
+
99
+ programs: {
100
+ // Read-only inspection.
101
+ ls: 'routine', ll: 'routine', dir: 'routine', pwd: 'routine', echo: 'routine', printf: 'routine', true: 'routine', false: 'routine',
102
+ ':': 'routine', test: 'routine', '[': 'routine', '[[': 'routine', which: 'routine', type: 'routine', whereis: 'routine', whoami: 'routine',
103
+ id: 'routine', uname: 'routine', hostname: 'routine', date: 'routine', uptime: 'routine', sleep: 'routine', seq: 'routine',
104
+ basename: 'routine', dirname: 'routine', realpath: 'routine', readlink: 'routine', cd: { handler: 'cd' }, pushd: 'routine', popd: 'routine',
105
+ export: { handler: 'export' }, unset: 'routine', set: 'routine', shopt: 'unknown', trap: 'unknown', wait: 'routine', jobs: 'routine', read: 'routine',
106
+ exit: 'routine', return: 'routine', local: { handler: 'export' }, declare: { handler: 'export' }, typeset: { handler: 'export' }, readonly: { handler: 'export' }, history: 'routine', help: 'routine',
107
+ hash: 'unknown', enable: 'unknown',
108
+ ps: 'routine', top: 'routine', htop: 'routine', free: 'routine', df: 'routine', du: { read: true }, lsof: 'routine', nproc: 'routine',
109
+ cat: { read: true }, head: { read: true }, tail: { read: true }, less: { read: true }, more: { read: true }, wc: { read: true },
110
+ grep: { read: true }, egrep: { read: true }, fgrep: { read: true }, rg: { handler: 'rg' }, ag: { read: true }, ack: { read: true },
111
+ find: { handler: 'find' }, fd: { handler: 'fd' }, tree: { read: true }, file: { read: true }, stat: { read: true }, diff: { read: true },
112
+ cmp: { read: true }, comm: { read: true }, sort: { read: true }, uniq: { read: true }, cut: { read: true }, tr: 'routine', paste: { read: true },
113
+ column: { read: true }, nl: { read: true }, fold: { read: true }, rev: { read: true }, tac: { read: true }, jq: { read: true }, yq: { read: true },
114
+ xxd: { read: true }, od: { read: true }, hexdump: { read: true }, strings: { read: true }, base64: { read: true }, base32: { read: true },
115
+ sha256sum: { read: true }, sha1sum: { read: true }, md5sum: { read: true }, shasum: { read: true }, cksum: { read: true },
116
+ awk: { handler: 'awk' }, gawk: { handler: 'awk' }, mawk: { handler: 'awk' }, sed: { handler: 'sed' },
117
+ env: 'routine', printenv: 'routine',
118
+ // Local file changes inside the workspace.
119
+ mkdir: { write: true }, touch: { write: true }, cp: { write: true }, mv: { write: true }, ln: { write: true }, tee: { write: true },
120
+ install: { write: true }, truncate: { write: true }, rmdir: { write: true }, rm: { handler: 'rm' }, unlink: { write: true },
121
+ chmod: { handler: 'chmod' }, chown: { handler: 'chmod' }, chgrp: { handler: 'chmod' },
122
+ tar: { handler: 'archive' }, zip: { read: true }, unzip: { handler: 'archive' }, gzip: { read: true }, gunzip: { read: true }, xz: { read: true }, unxz: { read: true }, bzip2: { read: true }, zstd: { read: true }, '7z': 'unknown',
123
+ patch: 'routine',
124
+ // Developer tools.
125
+ git: { handler: 'git' },
126
+ gh: { handler: 'gh' },
127
+ npm: { handler: 'pkg' }, pnpm: { handler: 'pkg' }, yarn: { handler: 'pkg' }, bun: { handler: 'bun' },
128
+ npx: { handler: 'npx' }, pnpx: { handler: 'npx' }, bunx: { handler: 'npx' },
129
+ tsc: 'routine', eslint: 'routine', prettier: 'routine', biome: 'routine', jest: 'routine', vitest: 'routine', mocha: 'routine',
130
+ // Dev servers, bundlers and git-hook installers: they run project config the agent may have
131
+ // written, so agentd's workspace taint escalates them after process-spawning code is written.
132
+ husky: 'routine', vite: 'routine', next: 'routine', webpack: 'routine', 'webpack-cli': 'routine', rollup: 'routine', esbuild: 'routine',
133
+ tsup: 'routine', parcel: 'routine', astro: 'routine', nuxi: 'routine', nuxt: 'routine', storybook: 'routine', gatsby: 'routine', remix: 'routine',
134
+ pytest: 'routine', ruff: 'routine', black: 'routine', mypy: 'routine', flake8: 'routine', pylint: 'routine', isort: 'routine',
135
+ shellcheck: 'routine', actionlint: 'routine', yamllint: 'routine', hadolint: 'routine', golangci_lint: 'routine', 'golangci-lint': 'routine',
136
+ gofmt: 'routine', rustfmt: 'routine', clang_format: 'routine', 'clang-format': 'routine', cloc: 'routine', tokei: 'routine',
137
+ node: { handler: 'interpreter' }, nodejs: { handler: 'interpreter' },
138
+ cargo: { sub: { build: 'routine', test: 'routine', check: 'routine', clippy: 'routine', fmt: 'routine', doc: 'routine', tree: 'routine', metadata: 'routine', bench: 'routine', add: 'routine', update: 'routine', search: 'routine', publish: 'risky', yank: 'risky', owner: 'risky', login: 'risky', logout: 'risky' }, default: 'unknown' },
139
+ go: { sub: { build: 'routine', test: 'routine', vet: 'routine', fmt: 'routine', mod: 'routine', list: 'routine', version: 'routine', env: 'routine', doc: 'routine', get: 'routine' }, default: 'unknown' },
140
+ pip: { handler: 'pip' }, pip3: { handler: 'pip' },
141
+ uv: { sub: { pip: 'routine', sync: 'routine', lock: 'routine', add: 'routine', venv: 'routine', tree: 'routine', publish: 'risky' }, default: 'unknown' },
142
+ poetry: { sub: { install: 'routine', show: 'routine', lock: 'routine', check: 'routine', add: 'routine', build: 'routine', publish: 'risky' }, default: 'unknown' },
143
+ twine: { sub: { check: 'routine', upload: 'risky' }, default: 'unknown' },
144
+ gem: { sub: { list: 'routine', install: 'routine', build: 'routine', push: 'risky', yank: 'risky', owner: 'risky' }, default: 'unknown' },
145
+ mvn: { sub: { compile: 'routine', test: 'routine', package: 'routine', verify: 'routine', clean: 'routine', deploy: 'risky', release: 'risky' }, default: 'unknown' },
146
+ dotnet: { sub: { build: 'routine', test: 'routine', restore: 'routine', format: 'routine', nuget: 'unknown' }, default: 'unknown' },
147
+ docker: { handler: 'docker' }, podman: { handler: 'docker' },
148
+ kubectl: { sub: { get: 'routine', describe: 'routine', logs: 'routine', explain: 'routine', version: 'routine', 'api-resources': 'routine', top: 'routine', diff: 'routine', config: 'unknown', apply: 'risky', delete: 'risky', exec: 'risky', scale: 'risky', rollout: 'risky', patch: 'risky', replace: 'risky', create: 'risky', edit: 'risky', drain: 'risky', cordon: 'risky', taint: 'risky', label: 'risky', annotate: 'risky', 'port-forward': 'unknown', cp: 'risky', run: 'risky', set: 'risky', auth: 'unknown' }, default: 'unknown', skip: 'kubectl' },
149
+ helm: { sub: { list: 'routine', ls: 'routine', status: 'routine', template: 'routine', lint: 'routine', show: 'routine', search: 'routine', repo: 'unknown', install: 'risky', upgrade: 'risky', uninstall: 'risky', delete: 'risky', rollback: 'risky' }, default: 'unknown' },
150
+ terraform: { sub: { plan: 'routine', validate: 'routine', fmt: 'routine', init: 'routine', show: 'routine', output: 'routine', providers: 'routine', graph: 'routine', version: 'routine', apply: 'risky', destroy: 'risky', import: 'risky', state: 'risky', taint: 'risky', untaint: 'risky', 'force-unlock': 'risky', workspace: 'unknown', console: 'unknown' }, default: 'unknown', skip: 'terraform' },
151
+ tofu: { sub: { plan: 'routine', validate: 'routine', fmt: 'routine', init: 'routine', show: 'routine', apply: 'risky', destroy: 'risky', import: 'risky', state: 'risky' }, default: 'unknown' },
152
+ pulumi: { sub: { preview: 'routine', stack: 'unknown', up: 'risky', destroy: 'risky', refresh: 'risky', cancel: 'risky' }, default: 'unknown' },
153
+ wrangler: { sub: { dev: 'routine', whoami: 'routine', types: 'routine', tail: 'routine', deploy: 'risky', publish: 'risky', secret: 'risky', rollback: 'risky', delete: 'risky', d1: 'risky', kv: 'unknown', r2: 'unknown', versions: 'unknown', login: 'risky' }, default: 'unknown' },
154
+ fly: { sub: { status: 'routine', logs: 'routine', version: 'routine', deploy: 'risky', ssh: 'risky', secrets: 'risky', scale: 'risky', destroy: 'risky', apps: 'unknown', postgres: 'risky', machine: 'risky', console: 'risky' }, default: 'unknown' },
155
+ flyctl: { sub: { status: 'routine', logs: 'routine', deploy: 'risky', ssh: 'risky', secrets: 'risky', scale: 'risky', destroy: 'risky' }, default: 'unknown' },
156
+ vercel: 'risky', netlify: 'risky', heroku: 'risky', firebase: 'risky', railway: 'risky', serverless: 'risky', sls: 'risky', cdk: 'risky', eksctl: 'risky',
157
+ aws: 'risky', gcloud: 'risky', az: 'risky', doctl: 'risky', supabase: 'risky', stripe: 'risky', 'ansible-playbook': 'risky', ansible: 'risky',
158
+ // Databases: opaque by default; SQL mutation text makes them risky.
159
+ psql: { handler: 'sql' }, mysql: { handler: 'sql' }, sqlite3: { handler: 'sql' }, mongosh: { handler: 'sql' }, mongo: { handler: 'sql' },
160
+ 'redis-cli': { handler: 'sql' }, cqlsh: { handler: 'sql' }, 'clickhouse-client': { handler: 'sql' }, pg_dump: 'unknown', pg_restore: 'risky', dropdb: 'risky', createdb: 'unknown',
161
+ // Network and remote execution.
162
+ curl: { handler: 'http' }, wget: { handler: 'http' }, http: { handler: 'http' }, https: { handler: 'http' }, xh: { handler: 'http' },
163
+ ssh: 'risky', scp: 'risky', sftp: 'risky', rsync: { handler: 'rsync' }, mosh: 'risky', telnet: 'risky', nc: 'risky', ncat: 'risky', socat: 'risky',
164
+ // System changes.
165
+ dd: 'risky', mkfs: 'risky', 'mkfs.ext4': 'risky', 'mkfs.xfs': 'risky', fdisk: 'risky', parted: 'risky', wipefs: 'risky', shred: 'risky',
166
+ shutdown: 'risky', reboot: 'risky', halt: 'risky', poweroff: 'risky', init: 'risky', iptables: 'risky', ufw: 'risky', nft: 'risky',
167
+ useradd: 'risky', userdel: 'risky', usermod: 'risky', passwd: 'risky', chpasswd: 'risky', visudo: 'risky', mount: 'risky', umount: 'risky',
168
+ swapoff: 'risky', crontab: { sub: { '-l': 'routine' }, default: 'risky', flags: true },
169
+ systemctl: { handler: 'systemctl' }, service: 'risky', launchctl: { sub: { list: 'routine', print: 'routine' }, default: 'risky' },
170
+ kill: { handler: 'kill' }, pkill: { handler: 'kill' }, killall: { handler: 'kill' },
171
+ // Marrow self-protection: control and uninstall are owner-only.
172
+ 'marrow-agentd': { sub: { status: 'routine', doctor: 'routine', version: 'routine', hook: 'routine', control: 'risky', stop: 'risky', uninstall: 'risky', run: 'risky' }, default: 'risky' },
173
+ 'marrow-install': { sub: { doctor: 'routine', status: 'routine', detect: 'routine', control: 'risky', uninstall: 'risky', agentd: 'risky' }, default: 'unknown' },
174
+ 'marrow-mcp': 'unknown',
175
+ // Opaque task runners: targets can do anything (ADV-06 H6); the server decides.
176
+ make: 'unknown', just: 'unknown', task: 'unknown', rake: 'unknown', invoke: 'unknown', nox: 'unknown', tox: 'unknown',
177
+ gradle: 'unknown', gradlew: 'unknown', turbo: 'unknown', nx: 'unknown', lerna: 'unknown',
178
+ eval: 'unknown', source: { handler: 'source' }, '.': { handler: 'source' }, alias: 'unknown',
179
+ open: 'unknown', 'xdg-open': 'unknown', code: 'unknown', vim: 'unknown', nvim: 'unknown', nano: 'unknown', emacs: 'unknown',
180
+ },
181
+
182
+ // Behaviour when the server gate cannot answer. Risky actions always fail closed.
183
+ outage: { risky: 'deny', unknown: 'deny' },
184
+ sub_flags_with_value: SUB_FLAGS_WITH_VALUE,
185
+ });
186
+
187
+ module.exports = { BASELINE_POLICY };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ // Redaction for anything the daemon sends to the server or writes to its queue or status. It
4
+ // removes secret-shaped values from argv and free text before they leave the process. Keys are
5
+ // never logged; this is a second line of defence for tool input the agent supplied.
6
+ //
7
+ // Input is truncated BEFORE any pattern runs and every quantifier is bounded, so redaction is
8
+ // linear in a small constant no matter how large the tool input is (a 48 KB path once took 10 s).
9
+
10
+ const SECRET_PATTERNS = [
11
+ /\bmrw_(?:live|test)_[A-Za-z0-9]{4,}/g,
12
+ /\bmrw_[A-Za-z0-9_]{8,}/g,
13
+ /\bmarrow_[A-Za-z0-9_]{8,}/g,
14
+ /\b(?:sk|pk|rk)-[A-Za-z0-9_-]{12,}/g,
15
+ /\bsk_(?:live|test)_[A-Za-z0-9]{8,}/g,
16
+ /\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{12,}/g,
17
+ /\bxox[abprs]-[A-Za-z0-9-]{8,}/g,
18
+ /\bAKIA[0-9A-Z]{12,}/g,
19
+ /\bAIza[0-9A-Za-z_-]{20,}/g,
20
+ /\bnpm_[A-Za-z0-9]{20,}/g,
21
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g,
22
+ /-----BEGIN [A-Z ]{0,40}PRIVATE KEY-----[\s\S]{0,8192}?(?:-----END [A-Z ]{0,40}PRIVATE KEY-----|$)/g,
23
+ /\b(?:bearer|token|basic)\s{1,4}[A-Za-z0-9._~+/=-]{12,}/gi,
24
+ /\b[A-Za-z0-9+/_-]{40,}={0,2}/g,
25
+ ];
26
+ const ASSIGNMENT_PATTERN = /\b([A-Za-z0-9_.-]{0,32}(?:key|token|secret|password|passwd|pwd|auth|credential|cookie|session)[A-Za-z0-9_.-]{0,32})\s{0,4}([=:])\s{0,4}("[^"]{0,512}"|'[^']{0,512}'|[^\s&"']{1,512})/gi;
27
+ const URL_CREDENTIALS = /(\b[a-z][a-z0-9+.-]{0,20}:\/\/)[^\s/@:]{1,256}:[^\s/@]{1,256}@/gi;
28
+
29
+ function redactText(value, maxLength = 200) {
30
+ const raw = String(value == null ? '' : value);
31
+ // Keep a little more than we return so a secret cut at the boundary is still recognised.
32
+ let text = raw.length > maxLength * 2 + 64 ? raw.slice(0, maxLength * 2 + 64) : raw;
33
+ text = text.replace(URL_CREDENTIALS, '$1[redacted]@');
34
+ text = text.replace(ASSIGNMENT_PATTERN, (_, name, sep) => `${name}${sep}[redacted]`);
35
+ for (const pattern of SECRET_PATTERNS) text = text.replace(pattern, '[redacted]');
36
+ text = text.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, ' ');
37
+ return text.length > maxLength || raw.length > text.length + 64 ? `${text.slice(0, maxLength)}…` : text;
38
+ }
39
+
40
+ // A short label token (reason code, program name, host): redacted and restricted to a safe
41
+ // alphabet so tool input can never smuggle a secret through a reason string.
42
+ function safeToken(value, maxLength = 64) {
43
+ return redactText(value, maxLength).replace(/[^A-Za-z0-9._:/@+\-[\]]/g, '_');
44
+ }
45
+
46
+ function redactArgv(argv, { maxArgs = 32, maxLength = 160 } = {}) {
47
+ const out = [];
48
+ let hideNext = false;
49
+ for (const raw of (argv || []).slice(0, maxArgs)) {
50
+ const arg = String(raw);
51
+ if (hideNext) { out.push('[redacted]'); hideNext = false; continue; }
52
+ if (/^(?:-H|--header|-u|--user|--password|--token|--otp|--api-key|--key|--secret|-p)$/i.test(arg)) { out.push(arg); hideNext = true; continue; }
53
+ out.push(redactText(arg, maxLength));
54
+ }
55
+ if ((argv || []).length > maxArgs) out.push(`[+${argv.length - maxArgs} args]`);
56
+ return out;
57
+ }
58
+
59
+ module.exports = { redactText, redactArgv, safeToken };