toolfunnel 0.7.0 → 0.7.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.
- package/README.md +228 -235
- package/SECURITY.md +84 -84
- package/bin/toolfunnel.js +451 -451
- package/docs/KNOWN_BUGS.md +80 -80
- package/docs/design.md +84 -84
- package/docs/packaging.md +115 -115
- package/hooks/hooks.manifest.json +27 -27
- package/hooks/scripts/example-audit.js +21 -21
- package/hooks/scripts/example-deny-dangerous.js +39 -39
- package/mcp/expose.example.json +30 -30
- package/mcp/servers/mock-modern/server.js +147 -147
- package/mcp/servers/mock-upstream/server.js +380 -380
- package/package.json +67 -67
- package/src/auth/config.js +174 -174
- package/src/auth/install.js +181 -181
- package/src/auth/resource-server.js +353 -353
- package/src/core/child-reaper.js +61 -61
- package/src/core/config-home.js +134 -134
- package/src/core/events.js +105 -105
- package/src/core/hook-engine.js +329 -329
- package/src/core/hook-loader.js +824 -824
- package/src/core/hook-runner.js +566 -566
- package/src/core/logger.js +297 -297
- package/src/core/matcher.js +100 -100
- package/src/core/metrics.js +75 -75
- package/src/core/rename-migration.js +163 -163
- package/src/core/requires.js +138 -138
- package/src/core/server-config.js +98 -98
- package/src/extend/add-hook.md +152 -152
- package/src/extend/add-mcp.md +119 -119
- package/src/extend/configure.md +88 -88
- package/src/extend/create-tool.md +168 -168
- package/src/extend/howto.js +102 -102
- package/src/extend/package.md +190 -190
- package/src/extend/wrap.md +99 -99
- package/src/mcp/aggregator.js +1719 -1674
- package/src/mcp/expose-store.js +577 -577
- package/src/mcp/gated-run.js +210 -210
- package/src/mcp/http-transport.js +1563 -1563
- package/src/mcp/mcp-client.js +1070 -1070
- package/src/mcp/modern.js +406 -406
- package/src/mcp/protocol.js +468 -468
- package/src/mcp/server.js +3112 -3112
- package/src/tools/registry.js +661 -661
- package/src/tools/tool-state.js +170 -170
- package/src/ui/public/app.js +1187 -1187
- package/src/ui/public/index.html +286 -286
- package/src/ui/public/styles.css +535 -535
- package/src/ui/server.js +1616 -1616
- package/tools/scripts/base64.js +139 -139
- package/tools/scripts/danger.js +61 -61
- package/tools/scripts/echo.js +39 -39
- package/tools/scripts/hash.js +101 -101
- package/tools/scripts/json.js +109 -109
- package/tools/scripts/text-stats.js +131 -131
- package/tools/scripts/tf-env.js +43 -43
- package/tools/scripts/tf-hook-add.js +151 -151
- package/tools/scripts/tf-hook-set.js +120 -120
- package/tools/scripts/tf-list.js +192 -192
- package/tools/scripts/tf-log.js +86 -86
- package/tools/scripts/tf-mcp-add.js +117 -117
- package/tools/scripts/tf-mcp-set.js +121 -121
- package/tools/scripts/tf-pack.js +237 -237
- package/tools/scripts/tf-tool-add.js +134 -134
- package/tools/scripts/tf-tool-set.js +142 -142
- package/tools/scripts/tf-wrap.js +103 -103
- package/tools/scripts/uuid.js +102 -102
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`.
|