# How the ToolMonitor Validates Claude Tool Calls Against Predefined Allowlists

> Learn how the ToolMonitor validates Claude tool calls using allowlists, disallowlists, and logging for robust security. Discover its three-stage validation process.

- Repository: [Richard A/claude-code-telegram](https://github.com/richardatct/claude-code-telegram)
- Tags: how-to-guide
- Published: 2026-02-20

---

**The `ToolMonitor` class in [`src/claude/monitor.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/claude/monitor.py) validates Claude tool calls through a three-stage process: checking allowlists, checking disallowlists, and providing a fast-path boolean helper, with all violations logged to `security_violations` and returned as error messages.**

The `RichardAtCT/claude-code-telegram` repository implements a security layer that intercepts AI-generated tool executions before they reach the system. Understanding how to validate Claude tool calls against predefined allowlists is essential for securing autonomous coding agents against unauthorized file system or shell access.

## Three-Stage Validation Process

The `ToolMonitor` class orchestrates validation through sequential checks defined in the `validate_tool_call()` method. Each stage operates independently, with early exits triggered only when the `DISABLE_TOOL_VALIDATION` environment variable is set to `true`.

### Allowlist Enforcement

If the `claude_allowed_tools` configuration is defined, the monitor strictly permits only tools present in that list. In [`src/claude/monitor.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/claude/monitor.py) at lines 75–81, the guard clause checks `if not self.disable_tool_validation and hasattr(self.config, "claude_allowed_tools")`. The actual membership test occurs on line 80: `if tool_name not in self.config.claude_allowed_tools:`. When a tool is absent from the allowlist, the monitor records a `disallowed_tool` violation in `self.security_violations` and returns `(False, error_message)`.

### Disallowlist Blocking

Conversely, if `claude_disallowed_tools` is configured, the monitor ensures the requested tool name is **not** present in that list. Lines 84–90 implement this check with the condition `if tool_name in self.config.claude_disallowed_tools:`. A match here generates an `explicitly_disallowed_tool` violation and rejects the call immediately. This dual-list approach allows administrators to operate in either explicit-allow or explicit-deny modes depending on security requirements.

### Fast-Path Helper for Pre-flight Checks

The `is_tool_allowed()` method (lines 106–122) provides a lightweight boolean check that reproduces the allowlist and disallowlist lookups without invoking the full validation flow. Other components use this helper when they need to determine permission status without processing path safety checks or logging full violations.

```python

# Quick boolean check without full validation

if monitor.is_tool_allowed("read_file"):
    print("read_file is permitted by the allowlist")

```

## Configuration and Environment Variables

Validation behavior is controlled through the `Settings` class defined in [`src/config/settings.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/config/settings.py). The system recognizes two primary configuration vectors:

- **Allowlist**: `CLAUDE_ALLOWED_TOOLS` environment variable (e.g., `"read_file,write_file,bash"`)
- **Disallowlist**: `CLAUDE_DISALLOWED_TOOLS` environment variable (e.g., `"shell"`)

When `DISABLE_TOOL_VALIDATION` is set to `true`, the monitor sets `self.disable_tool_validation` to `True`, causing both list checks to be skipped entirely (lines 66–73). Note that path and command safety validations remain active regardless of this setting, as they protect the file system independently of tool-name validation.

```python

# Example: configuring allow- and disallow-lists via Settings (env-vars or .env)

#   CLAUDE_ALLOWED_TOOLS="read_file,write_file,bash"

#   CLAUDE_DISALLOWED_TOOLS="shell"

from src.config.settings import Settings
from src.claude.monitor import ToolMonitor

settings = Settings()                # loads env-vars

monitor = ToolMonitor(settings)      # instantiate the monitor

# Simulate a Claude tool call

tool_name = "bash"
tool_input = {"command": "ls -l /tmp"}
allowed, error = await monitor.validate_tool_call(
    tool_name,
    tool_input,
    working_directory=Path("/app/workdir"),
    user_id=12345,
)

if allowed:
    print("Tool call approved")
else:
    print(f"Rejected: {error}")

```

## Violation Handling and Logging

All validation failures are appended to `self.security_violations` (see lines 82–90 and 94–101) and logged using `structlog` for audit trails. The `validate_tool_call()` method returns a tuple of `(bool, Optional[str])`, where the boolean indicates approval status and the optional string contains the specific rejection reason. This design allows calling code to distinguish between security violations and system errors while maintaining detailed forensic logs.

## Summary

- The `ToolMonitor` class in [`src/claude/monitor.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/claude/monitor.py) implements allowlist and disallowlist validation for Claude-generated tool calls.
- **Allowlist checks** (lines 75–81) reject any tool not explicitly listed in `claude_allowed_tools`.
- **Disallowlist checks** (lines 84–90) reject tools specifically forbidden in `claude_disallowed_tools`.
- Setting `DISABLE_TOOL_VALIDATION=true` bypasses name-based checks (lines 66–73) but preserves path safety validations.
- The `is_tool_allowed()` method (lines 106–122) offers a lightweight boolean pre-check for components needing quick permission lookups.
- All violations are stored in `security_violations` and logged via `structlog`, with errors returned as `(False, error_message)` tuples.

## Frequently Asked Questions

### How do I configure which tools Claude is allowed to execute?

Set the `CLAUDE_ALLOWED_TOOLS` environment variable to a comma-separated list of permitted tool names (e.g., `read_file,write_file,bash`). The `Settings` class in [`src/config/settings.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/config/settings.py) loads this value, and the `ToolMonitor` enforces it during the allowlist check at lines 75–81 of [`src/claude/monitor.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/claude/monitor.py).

### What happens if a tool appears on both the allowlist and disallowlist?

The disallowlist check (lines 84–90) executes after the allowlist check (lines 75–81). If a tool name appears in both lists, the allowlist check passes first, but the subsequent disallowlist check detects the conflict and rejects the call with an `explicitly_disallowed_tool` violation. The disallowlist acts as a final override.

### Can I disable tool validation entirely without removing the configuration?

Yes. Set the environment variable `DISABLE_TOOL_VALIDATION=true`. This sets `self.disable_tool_validation` to `True`, causing the monitor to skip both the allowlist and disallowlist checks (lines 66–73). However, path and command safety validations in [`src/security/validators.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/security/validators.py) remain active to protect the file system.

### What is the difference between `validate_tool_call()` and `is_tool_allowed()`?

`validate_tool_call()` performs complete validation including allowlist/disallowlist checks, path safety validation, and violation logging, returning a `(bool, Optional[str])` tuple. `is_tool_allowed()` (lines 106–122) provides a fast-path boolean check that only evaluates the list memberships without side effects, suitable for UI pre-checks or permission previews.