BerriAI/litellm · error · HTTPException
Content blocked: {context_label} argument matched a masking
Error message
Content blocked: {context_label} argument matched a masking rule on a non-rewritable field What it means
The content filter guardrail walks MCP tool-call arguments and masks text that matches masking rules. Dict keys and numeric scalars cannot be rewritten without breaking the tool schema, so the guardrail instead asserts they are clean: if _filter_single_text would change the text (it matches a masking rule), the whole request is blocked with HTTP 400. context_label identifies which argument scope (e.g. which tool's arguments) failed.
Source
Thrown at litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py:1760
start_time=start_time.timestamp(),
end_time=datetime.now().timestamp(),
duration=(datetime.now() - start_time).total_seconds(),
masked_entity_count=masked_entity_count,
tracing_detail=GuardrailTracingDetail(**tracing_kw),
)
@staticmethod
def _get_mcp_tool_name(request_data: dict) -> str | None:
raw_name: Final[object] = request_data.get("mcp_tool_name")
if isinstance(raw_name, str) and raw_name:
return raw_name
return None
def _assert_argument_label_clean(
self, text: str, detections: list[ContentFilterDetection], context_label: str
) -> None:
if self._filter_single_text(text, detections=detections) != text:
raise HTTPException(
status_code=400,
detail={
"error": (
f"Content blocked: {context_label} argument matched a masking rule on a non-rewritable field"
)
},
)
def _filter_argument_value(
self,
value: object,
detections: list[ContentFilterDetection],
context_label: str,
depth: int = 0,
) -> object:
if depth > DEFAULT_MAX_RECURSE_DEPTH:
raise HTTPException(
status_code=400,View on GitHub (pinned to 77b7c6c40c)
Solutions
- Rename the offending dict key so it no longer matches the masking pattern - keys are validated, never masked
- Move sensitive-looking data into string values, which the masker rewrites in place instead of blocking
- Adjust the guardrail's masking rules/categories so they do not apply to MCP argument labels
- Remove PII-shaped literals (card/SSN-like numbers, emails) from argument keys and numeric fields
Example fix
# before - PII in a dict key: cannot be rewritten, request is blocked
tool_arguments = {"user@corp.com": "notify"}
# after - PII moved into a string value: gets masked, request proceeds
tool_arguments = {"contact": "user@corp.com"} Defensive patterns
Strategy: validation
Validate before calling
def assert_mcp_args_labels_clean(args: dict, mask) -> None:
"""Mirror of the guardrail check: keys and scalars must survive masking unchanged."""
def walk(node, depth=0):
if depth > 100:
raise ValueError("arguments too deep")
if isinstance(node, dict):
for k, v in node.items():
if isinstance(k, str) and mask(k) != k:
raise ValueError(f"dict key matches a masking rule and cannot be rewritten: {k!r}")
walk(v, depth + 1)
elif isinstance(node, (list, tuple)):
for v in node:
walk(v, depth + 1)
elif isinstance(node, (int, float)) and not isinstance(node, bool):
if mask(str(node)) != str(node):
raise ValueError(f"numeric value matches a masking rule: {node}")
walk(args) Type guard
def is_safe_tool_arguments(args: object) -> bool:
"""True when all dict keys are plain identifiers with no PII-shaped text."""
import re
piiish = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+|\d[\d\s-]{11,}")
def keys_clean(node):
if isinstance(node, dict):
return all(isinstance(k, str) and not piiish.search(k) and keys_clean(v) for k, v in node.items())
if isinstance(node, (list, tuple)):
return all(keys_clean(v) for v in node)
return True
return keys_clean(args) Prevention
- Never put user-supplied text into dict keys of tool arguments - use fixed key names and put variable data in string values
- Avoid PII-shaped test numbers (card-like digit strings) in argument payloads
- Add a pre-send lint in your MCP client that regex-scans keys for emails, phones, and long digit runs
When it happens
Trigger: An MCP tool call whose arguments dict contains a key, or a numeric value whose string form, matches a masking pattern - e.g. a key literally containing an email address, phone number, or card-shaped digits, or a test number like 4111111111111111 passed as an int argument.
Common situations: Dynamically built argument dicts where user-supplied text leaks into keys; human-readable field names that accidentally trip PII regexes; test fixtures using card/SSN-shaped numbers; serialization code that puts identifiers into dict keys instead of values.
Related errors
- Content blocked: {context_label} arguments exceed the maximu
- guardrail_violation
- Content blocked: competitor comparison or ranking intent det
- MCP Security: request references unregistered MCP server(s):
- invalid_request
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/9ad817574613f60e.
Report an issue: GitHub.