Skip to content

fix(mcp): enforce SystemUtils enabled_tools allow-list - #597

Open
rohanbuilds-ai wants to merge 1 commit into
GenAI-Security-Project:mainfrom
rohanbuilds-ai:fix/systemutils-enabled-tools
Open

rohanbuilds-ai wants to merge 1 commit into
GenAI-Security-Project:mainfrom
rohanbuilds-ai:fix/systemutils-enabled-tools

Conversation

@rohanbuilds-ai

Copy link
Copy Markdown

Summary

Enforces the configured enabled_tools allow-list on the SystemUtils MCP server so that only explicitly permitted tools are registered with FastMCP and exposed to agents.

Problem & Reproduction

In finbot/mcp/servers/systemutils/server.py, DEFAULT_CONFIG defines an "enabled_tools" allow-list containing 8 tools:

DEFAULT_CONFIG: dict[str, Any] = {
    "enabled_tools": [
        "run_diagnostics",
        "manage_storage",
        "rotate_logs",
        "database_maintenance",
        "network_request",
        "read_config",
        "manage_users",
        "execute_script",
    ],
    ...
}

When create_systemutils_server is invoked, it merges the server configuration:

config = {**DEFAULT_CONFIG, **(server_config or {})}

However, every tool function was statically and unconditionally decorated with @mcp.tool. The enabled_tools configuration was never referenced or checked.

Reproduction

server = create_systemutils_server(
    session_context,
    server_config={"enabled_tools": ["run_diagnostics", "read_config"]},
)
# Introspecting via FastMCP client:
tools = await client.list_tools()
# Observed (before fix): 8 tools returned, including execute_script and manage_users
# Expected: Only 2 tools ('run_diagnostics', 'read_config')

Security Impact

  • Least-Privilege Sandboxing: FinBot allows administrators (/api/v1/admin/mcp/servers/systemutils) and challenge configs to restrict server capabilities. Because enabled_tools was ignored, administrative tools (manage_users) and arbitrary script execution tools (execute_script) remained active and exposed to LLM agents even when restricted.
  • Attack Surface Exposure: In prompt injection or tool poisoning scenarios, compromised agents could discover and invoke high-privilege tools that were intended to be restricted or disabled for that workflow.

Solution

  1. finbot/mcp/servers/systemutils/server.py:
    • Extracted enabled_set from config.get("enabled_tools").
    • Defined a conditional _tool decorator that only registers a function with @mcp.tool if its __name__ is present in enabled_set.
    • Handled edge cases: if enabled_tools is None or non-iterable, fall back to DEFAULT_CONFIG["enabled_tools"]; bare strings are coerced to single-item lists.
    • Preserved mock server execution, docstrings, type annotations, and CTF logging behavior.
  2. tests/unit/mcp/test_systemutils_server.py:
    • Added 14 unit tests covering:
      • Default configuration enabling all 8 tools.
      • Empty configuration {} enabling all 8 tools.
      • Fallback to defaults when enabled_tools is None or non-iterable.
      • Subset allow-lists exposing only selected tools.
      • Single tool allow-lists.
      • Empty list enabled_tools=[] exposing 0 tools.
      • String coercion and tuple/set iterable support.
      • Safe handling of unrecognized tool names in allow-list.
      • Successful execution of enabled tools.
      • Rejection of disabled tools with fastmcp.exceptions.ToolError: Unknown tool.
      • Individual functionality verification for all 8 tools.
      • Compatibility with factory tool overrides (_apply_tool_overrides).

Verification Results

  • uv run pytest tests/unit/mcp/test_systemutils_server.py -v: 14 passed in 0.80s.
  • uv run pytest tests/unit/mcp/ tests/unit/labs/ -q: 89 passed in 10.36s.
  • uv run isort --check-only finbot/mcp/servers/systemutils/server.py tests/unit/mcp/test_systemutils_server.py: Clean.
  • uv run black --check finbot/mcp/servers/systemutils/server.py tests/unit/mcp/test_systemutils_server.py: Clean.
  • git diff --check: Clean (0 whitespace errors).

SystemUtils MCP server accepted an enabled_tools configuration setting defined in DEFAULT_CONFIG, but create_systemutils_server statically decorated all 8 tool functions with @mcp.tool. Consequently, all tools (including administrative and arbitrary script execution tools) were exposed and callable even when administrators or challenge setups restricted enabled_tools.

- Conditionally register tools with FastMCP only when their name appears in enabled_tools.
- Gracefully fall back to defaults when enabled_tools is None or non-iterable.
- Ensure disabled tools are excluded from list_tools() and reject calls with ToolError.
- Add comprehensive test suite in tests/unit/mcp/test_systemutils_server.py.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant