@getmarrow/mcp 3.9.102 → 3.9.103

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 (109) hide show
  1. package/README.md +49 -23
  2. package/dist/cli.js +41 -10
  3. package/dist/cli.js.map +1 -1
  4. package/dist/codex-plugin-pins.d.ts +19 -0
  5. package/dist/codex-plugin-pins.d.ts.map +1 -0
  6. package/dist/codex-plugin-pins.js +43 -0
  7. package/dist/codex-plugin-pins.js.map +1 -0
  8. package/dist/codex-plugins.d.ts +34 -0
  9. package/dist/codex-plugins.d.ts.map +1 -0
  10. package/dist/codex-plugins.js +240 -0
  11. package/dist/codex-plugins.js.map +1 -0
  12. package/dist/hook-contract.d.ts +2 -2
  13. package/dist/hook-contract.d.ts.map +1 -1
  14. package/dist/hook-contract.js +8 -3
  15. package/dist/hook-contract.js.map +1 -1
  16. package/dist/hook-pre-action.d.ts +48 -2
  17. package/dist/hook-pre-action.d.ts.map +1 -1
  18. package/dist/hook-pre-action.js +195 -22
  19. package/dist/hook-pre-action.js.map +1 -1
  20. package/dist/hook.d.ts +1 -1
  21. package/dist/hook.d.ts.map +1 -1
  22. package/dist/hook.js +12 -6
  23. package/dist/hook.js.map +1 -1
  24. package/dist/host-session.d.ts +11 -0
  25. package/dist/host-session.d.ts.map +1 -1
  26. package/dist/host-session.js +49 -0
  27. package/dist/host-session.js.map +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +15 -10
  30. package/dist/index.js.map +1 -1
  31. package/dist/normalized-action.d.ts +29 -6
  32. package/dist/normalized-action.d.ts.map +1 -1
  33. package/dist/normalized-action.js +492 -102
  34. package/dist/normalized-action.js.map +1 -1
  35. package/dist/own-server-identity.d.ts +123 -0
  36. package/dist/own-server-identity.d.ts.map +1 -0
  37. package/dist/own-server-identity.js +1052 -0
  38. package/dist/own-server-identity.js.map +1 -0
  39. package/dist/own-tool.d.ts +7 -3
  40. package/dist/own-tool.d.ts.map +1 -1
  41. package/dist/own-tool.js +7 -3
  42. package/dist/own-tool.js.map +1 -1
  43. package/dist/redact.d.ts +23 -0
  44. package/dist/redact.d.ts.map +1 -1
  45. package/dist/redact.js +181 -8
  46. package/dist/redact.js.map +1 -1
  47. package/dist/seatbelt/classifier-pin.d.ts +15 -0
  48. package/dist/seatbelt/classifier-pin.d.ts.map +1 -0
  49. package/dist/seatbelt/classifier-pin.js +26 -0
  50. package/dist/seatbelt/classifier-pin.js.map +1 -0
  51. package/dist/seatbelt/guards.d.ts +10 -0
  52. package/dist/seatbelt/guards.d.ts.map +1 -0
  53. package/dist/seatbelt/guards.js +509 -0
  54. package/dist/seatbelt/guards.js.map +1 -0
  55. package/dist/seatbelt/hook.d.ts +72 -0
  56. package/dist/seatbelt/hook.d.ts.map +1 -0
  57. package/dist/seatbelt/hook.js +362 -0
  58. package/dist/seatbelt/hook.js.map +1 -0
  59. package/dist/seatbelt/longopts.d.ts +68 -0
  60. package/dist/seatbelt/longopts.d.ts.map +1 -0
  61. package/dist/seatbelt/longopts.js +377 -0
  62. package/dist/seatbelt/longopts.js.map +1 -0
  63. package/dist/seatbelt/mapper.d.ts +83 -0
  64. package/dist/seatbelt/mapper.d.ts.map +1 -0
  65. package/dist/seatbelt/mapper.js +10248 -0
  66. package/dist/seatbelt/mapper.js.map +1 -0
  67. package/dist/seatbelt/record.d.ts +94 -0
  68. package/dist/seatbelt/record.d.ts.map +1 -0
  69. package/dist/seatbelt/record.js +776 -0
  70. package/dist/seatbelt/record.js.map +1 -0
  71. package/dist/seatbelt/tables.d.ts +82 -0
  72. package/dist/seatbelt/tables.d.ts.map +1 -0
  73. package/dist/seatbelt/tables.js +359 -0
  74. package/dist/seatbelt/tables.js.map +1 -0
  75. package/dist/seatbelt-deploy.d.ts +100 -0
  76. package/dist/seatbelt-deploy.d.ts.map +1 -0
  77. package/dist/seatbelt-deploy.js +393 -0
  78. package/dist/seatbelt-deploy.js.map +1 -0
  79. package/dist/seatbelt-fs.d.ts +30 -0
  80. package/dist/seatbelt-fs.d.ts.map +1 -0
  81. package/dist/seatbelt-fs.js +94 -0
  82. package/dist/seatbelt-fs.js.map +1 -0
  83. package/dist/seatbelt-git.d.ts +212 -0
  84. package/dist/seatbelt-git.d.ts.map +1 -0
  85. package/dist/seatbelt-git.js +844 -0
  86. package/dist/seatbelt-git.js.map +1 -0
  87. package/dist/seatbelt-mode.d.ts +77 -0
  88. package/dist/seatbelt-mode.d.ts.map +1 -0
  89. package/dist/seatbelt-mode.js +496 -0
  90. package/dist/seatbelt-mode.js.map +1 -0
  91. package/dist/seatbelt-path.d.ts +123 -0
  92. package/dist/seatbelt-path.d.ts.map +1 -0
  93. package/dist/seatbelt-path.js +482 -0
  94. package/dist/seatbelt-path.js.map +1 -0
  95. package/dist/seatbelt-scripts.d.ts +109 -0
  96. package/dist/seatbelt-scripts.d.ts.map +1 -0
  97. package/dist/seatbelt-scripts.js +1060 -0
  98. package/dist/seatbelt-scripts.js.map +1 -0
  99. package/dist/tool-catalog.js +2 -2
  100. package/dist/tool-catalog.js.map +1 -1
  101. package/dist/types.d.ts +2 -1
  102. package/dist/types.d.ts.map +1 -1
  103. package/package.json +2 -1
  104. package/server.json +3 -3
  105. package/vendor/classifier/MANIFEST.json +25 -0
  106. package/vendor/classifier/classifier.js +1356 -0
  107. package/vendor/classifier/policy-baseline.js +187 -0
  108. package/vendor/classifier/redact.js +59 -0
  109. 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 };