toolfunnel 0.7.0 → 0.8.0

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 (73) hide show
  1. package/README.md +238 -235
  2. package/SECURITY.md +84 -84
  3. package/bin/toolfunnel.js +530 -451
  4. package/docs/KNOWN_BUGS.md +100 -80
  5. package/docs/MANUAL.pdf +0 -0
  6. package/docs/design.md +84 -84
  7. package/docs/packaging.md +116 -115
  8. package/hooks/hooks.manifest.json +27 -27
  9. package/hooks/scripts/example-audit.js +21 -21
  10. package/hooks/scripts/example-deny-dangerous.js +39 -39
  11. package/mcp/expose.example.json +30 -30
  12. package/mcp/servers/mock-modern/server.js +147 -147
  13. package/mcp/servers/mock-upstream/server.js +379 -380
  14. package/package.json +67 -67
  15. package/src/auth/config.js +172 -174
  16. package/src/auth/install.js +181 -181
  17. package/src/auth/resource-server.js +358 -353
  18. package/src/core/atomic-write.js +97 -0
  19. package/src/core/child-reaper.js +61 -61
  20. package/src/core/config-home.js +226 -134
  21. package/src/core/events.js +105 -105
  22. package/src/core/hook-engine.js +304 -329
  23. package/src/core/hook-loader.js +766 -824
  24. package/src/core/hook-runner.js +615 -566
  25. package/src/core/logger.js +295 -297
  26. package/src/core/matcher.js +100 -100
  27. package/src/core/metrics.js +90 -75
  28. package/src/core/output-cap.js +31 -0
  29. package/src/core/rename-migration.js +178 -163
  30. package/src/core/requires.js +138 -138
  31. package/src/core/server-config.js +98 -98
  32. package/src/extend/add-hook.md +155 -152
  33. package/src/extend/add-mcp.md +119 -119
  34. package/src/extend/configure.md +88 -88
  35. package/src/extend/create-tool.md +168 -168
  36. package/src/extend/howto.js +102 -102
  37. package/src/extend/package.md +190 -190
  38. package/src/extend/wrap.md +99 -99
  39. package/src/mcp/aggregator.js +2248 -1674
  40. package/src/mcp/expose-store.js +755 -577
  41. package/src/mcp/gated-run.js +210 -210
  42. package/src/mcp/http-transport.js +1657 -1563
  43. package/src/mcp/mcp-client.js +1174 -1070
  44. package/src/mcp/modern.js +409 -406
  45. package/src/mcp/protocol.js +473 -468
  46. package/src/mcp/server.js +3317 -3112
  47. package/src/mcp/tool-approvals.js +396 -0
  48. package/src/tools/registry.js +607 -661
  49. package/src/tools/tool-state.js +170 -170
  50. package/src/ui/public/app.js +1246 -1187
  51. package/src/ui/public/index.html +286 -286
  52. package/src/ui/public/styles.css +535 -535
  53. package/src/ui/server.js +1742 -1616
  54. package/tools/scripts/base64.js +139 -139
  55. package/tools/scripts/danger.js +61 -61
  56. package/tools/scripts/echo.js +39 -39
  57. package/tools/scripts/hash.js +101 -101
  58. package/tools/scripts/json.js +109 -109
  59. package/tools/scripts/text-stats.js +131 -131
  60. package/tools/scripts/tf-accept-change.js +67 -0
  61. package/tools/scripts/tf-env.js +43 -43
  62. package/tools/scripts/tf-hook-add.js +151 -151
  63. package/tools/scripts/tf-hook-set.js +120 -120
  64. package/tools/scripts/tf-list.js +192 -192
  65. package/tools/scripts/tf-log.js +86 -86
  66. package/tools/scripts/tf-mcp-add.js +130 -117
  67. package/tools/scripts/tf-mcp-set.js +121 -121
  68. package/tools/scripts/tf-pack.js +237 -237
  69. package/tools/scripts/tf-tool-add.js +134 -134
  70. package/tools/scripts/tf-tool-set.js +142 -142
  71. package/tools/scripts/tf-wrap.js +103 -103
  72. package/tools/scripts/uuid.js +102 -102
  73. package/tools/tools.register.json +16 -5
package/SECURITY.md CHANGED
@@ -1,84 +1,84 @@
1
- # Security Policy
2
-
3
- ToolFunnel sits between MCP clients and MCP servers - it gates, filters, and forwards
4
- tool calls. That makes its security surface worth taking seriously, and reports are
5
- genuinely welcome.
6
-
7
- ## Reporting a vulnerability
8
-
9
- **Please use GitHub's private vulnerability reporting:** go to the
10
- [Security tab](https://github.com/Rendeverance/toolfunnel/security) → **Report a
11
- vulnerability**. That opens a private thread with the maintainer - nothing is public
12
- until a fix is out.
13
-
14
- Please **do not** open a public issue for a security-sensitive report. If you're unsure
15
- whether something is security-relevant, please err on the side of the private route.
16
-
17
- ## What counts
18
-
19
- Anything that lets a client or upstream server do what the configuration says it
20
- shouldn't. For example:
21
-
22
- - Bypassing the tool gate / hidden-tool filtering (`tf_tool_set`, expose config)
23
- - Auth bypass in the HTTP transport or OAuth resource-server validation
24
- - Token or credential leakage (in logs, error messages, or forwarded traffic)
25
- - Escaping the PreToolUse deny-hook
26
- - Injection via tool names, schemas, or forwarded arguments
27
-
28
- Bugs that crash ToolFunnel but don't cross a trust boundary are ordinary bugs -
29
- public issues are fine for those.
30
-
31
- ## What to expect
32
-
33
- This is a single-maintainer project. Reports get a response on a best-effort basis -
34
- normally within a few days. Confirmed vulnerabilities are fixed as a priority, and
35
- you'll be credited in the release notes unless you'd rather not be.
36
-
37
- ## Supported versions
38
-
39
- Only the latest published release (`npm install toolfunnel`) is supported with
40
- security fixes.
41
-
42
- ## Trust model
43
-
44
- **The config home is the trust boundary.** Anyone who can write the config home
45
- (`tools/`, `mcp/`, `hooks/`, `auth/`, `toolfunnel.json`) can define register tools, hooks,
46
- and upstream commands that execute with the gateway process's privileges. Protect it with
47
- filesystem permissions like you would a crontab or a shell profile.
48
-
49
- Everything else follows from that line:
50
-
51
- - **Upstream MCP servers are operator-chosen code.** Attaching or wrapping a server means
52
- trusting it with whatever its command can reach. In funnel mode the gateway *refuses*
53
- upstream arguments, working directories, and code-loading environment variables that
54
- reference paths outside the config home. The per-upstream opt-outs are explicit, and each
55
- is wider than a single check: `allowOutsidePaths` lifts the outside-path refusal for
56
- everything path-shaped that upstream carries - its arguments (including the value after
57
- `=` in a `--flag=value` form), the working directory its bare arguments resolve against,
58
- and its path-list environment variables; `allowCodeLoadingEnv` permits the code-loading
59
- environment class (`NODE_OPTIONS`, `NODE_PATH`, and their equivalents for other runtimes)
60
- on its own. Wrap mode permits outside paths with a security notice, because serving your
61
- real files is usually the point of a wrapped server. The guard is a tripwire for configs that quietly reach
62
- outside an auditable config home - not a sandbox against an untrusted pack author:
63
- installing a pack is installing software.
64
- - **The network surface binds loopback by default** (HTTP, SSE, and the web UI), validates
65
- the `Origin` header against loopback origins, and enforces the protocol-version
66
- and parameter-mirroring header rules of the 2026-07-28 revision. Exposing the port beyond
67
- localhost is an operator decision; put OAuth 2.1 (opt-in) or your own reverse proxy in
68
- front of it if you do.
69
- - **Zero runtime dependencies** - the supply-chain audit surface is this repository's own
70
- code (OAuth, if enabled, adds exactly one audited library: `jose`).
71
- - **An unreadable gate decision is a deny.** A hook that floods its output past the cap, whose
72
- decision JSON cannot be parsed, or whose decision slot carries a value outside the exact
73
- protocol vocabulary (a miscased `"DENY"`, a synonym like `"denied"`, the right word in the
74
- wrong slot, a wrong type) blocks the call it was gating rather than waving it through. A hook
75
- matcher that does not compile is the same class: it is refused at authoring, and one already
76
- in the manifest denies every tool-bearing call of its event until fixed. A `null` in a
77
- protocol slot is NOT an attempt - it reads as "no value", exactly like an absent key, and
78
- falls through. The deliberate exception: a hook script that CRASHES, is MISSING, or HANGS
79
- past its timeout is a non-blocking error and the call proceeds - Claude Code hook parity, so
80
- a script-execution failure cannot take every tool offline. An authored-config defect (the
81
- matcher, the decision vocabulary) gets no such grace: the author wrote a gate the runner
82
- cannot read, and it denies until corrected. Denies are `exit 2`; a gate that cannot reach a
83
- decision should exit 2 explicitly rather than error out. Pinned by
84
- `test/gate-semantics.test.js` and `test/matcher-compile.test.js`.
1
+ # Security Policy
2
+
3
+ ToolFunnel sits between MCP clients and MCP servers - it gates, filters, and forwards
4
+ tool calls. That makes its security surface worth taking seriously, and reports are
5
+ genuinely welcome.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ **Please use GitHub's private vulnerability reporting:** go to the
10
+ [Security tab](https://github.com/Rendeverance/toolfunnel/security) → **Report a
11
+ vulnerability**. That opens a private thread with the maintainer - nothing is public
12
+ until a fix is out.
13
+
14
+ Please **do not** open a public issue for a security-sensitive report. If you're unsure
15
+ whether something is security-relevant, please err on the side of the private route.
16
+
17
+ ## What counts
18
+
19
+ Anything that lets a client or upstream server do what the configuration says it
20
+ shouldn't. For example:
21
+
22
+ - Bypassing the tool gate / hidden-tool filtering (`tf_tool_set`, expose config)
23
+ - Auth bypass in the HTTP transport or OAuth resource-server validation
24
+ - Token or credential leakage (in logs, error messages, or forwarded traffic)
25
+ - Escaping the PreToolUse deny-hook
26
+ - Injection via tool names, schemas, or forwarded arguments
27
+
28
+ Bugs that crash ToolFunnel but don't cross a trust boundary are ordinary bugs -
29
+ public issues are fine for those.
30
+
31
+ ## What to expect
32
+
33
+ This is a single-maintainer project. Reports get a response on a best-effort basis -
34
+ normally within a few days. Confirmed vulnerabilities are fixed as a priority, and
35
+ you'll be credited in the release notes unless you'd rather not be.
36
+
37
+ ## Supported versions
38
+
39
+ Only the latest published release (`npm install toolfunnel`) is supported with
40
+ security fixes.
41
+
42
+ ## Trust model
43
+
44
+ **The config home is the trust boundary.** Anyone who can write the config home
45
+ (`tools/`, `mcp/`, `hooks/`, `auth/`, `toolfunnel.json`) can define register tools, hooks,
46
+ and upstream commands that execute with the gateway process's privileges. Protect it with
47
+ filesystem permissions like you would a crontab or a shell profile.
48
+
49
+ Everything else follows from that line:
50
+
51
+ - **Upstream MCP servers are operator-chosen code.** Attaching or wrapping a server means
52
+ trusting it with whatever its command can reach. In funnel mode the gateway *refuses*
53
+ upstream arguments, working directories, and code-loading environment variables that
54
+ reference paths outside the config home. The per-upstream opt-outs are explicit, and each
55
+ is wider than a single check: `allowOutsidePaths` lifts the outside-path refusal for
56
+ everything path-shaped that upstream carries - its arguments (including the value after
57
+ `=` in a `--flag=value` form), the working directory its bare arguments resolve against,
58
+ and its path-list environment variables; `allowCodeLoadingEnv` permits the code-loading
59
+ environment class (`NODE_OPTIONS`, `NODE_PATH`, and their equivalents for other runtimes)
60
+ on its own. Wrap mode permits outside paths with a security notice, because serving your
61
+ real files is usually the point of a wrapped server. The guard is a tripwire for configs that quietly reach
62
+ outside an auditable config home - not a sandbox against an untrusted pack author:
63
+ installing a pack is installing software.
64
+ - **The network surface binds loopback by default** (HTTP, SSE, and the web UI), validates
65
+ the `Origin` header against loopback origins, and enforces the protocol-version
66
+ and parameter-mirroring header rules of the 2026-07-28 revision. Exposing the port beyond
67
+ localhost is an operator decision; put OAuth 2.1 (opt-in) or your own reverse proxy in
68
+ front of it if you do.
69
+ - **Zero runtime dependencies** - the supply-chain audit surface is this repository's own
70
+ code (OAuth, if enabled, adds exactly one audited library: `jose`).
71
+ - **An unreadable gate decision is a deny.** A hook that floods its output past the cap, whose
72
+ decision JSON cannot be parsed, or whose decision slot carries a value outside the exact
73
+ protocol vocabulary (a miscased `"DENY"`, a synonym like `"denied"`, the right word in the
74
+ wrong slot, a wrong type) blocks the call it was gating rather than waving it through. A hook
75
+ matcher that does not compile is the same class: it is refused at authoring, and one already
76
+ in the manifest denies every tool-bearing call of its event until fixed. A `null` in a
77
+ protocol slot is NOT an attempt - it reads as "no value", exactly like an absent key, and
78
+ falls through. The deliberate exception: a hook script that CRASHES, is MISSING, or HANGS
79
+ past its timeout is a non-blocking error and the call proceeds - Claude Code hook parity, so
80
+ a script-execution failure cannot take every tool offline. An authored-config defect (the
81
+ matcher, the decision vocabulary) gets no such grace: the author wrote a gate the runner
82
+ cannot read, and it denies until corrected. Denies are `exit 2`; a gate that cannot reach a
83
+ decision should exit 2 explicitly rather than error out. Pinned by
84
+ `test/gate-semantics.test.js` and `test/matcher-compile.test.js`.