{"record":{"id":"444a865cdd999973","repo":"BerriAI/litellm","slug":"error-loading-blocked-words-file-file-path-e","errorCode":null,"errorMessage":"Error loading blocked words file {file_path}: {e}","messagePattern":"Error loading blocked words file (.+?): (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py","lineNumber":812,"sourceCode":"            if not isinstance(data, dict) or \"blocked_words\" not in data:\n                raise ValueError(\"Invalid format: file must contain 'blocked_words' key with list of words\")\n\n            for word_data in data[\"blocked_words\"]:\n                if not isinstance(word_data, dict) or \"keyword\" not in word_data or \"action\" not in word_data:\n                    verbose_proxy_logger.warning(\"Skipping invalid word entry: %s\", word_data)\n                    continue\n\n                keyword = word_data[\"keyword\"].lower()\n                action = ContentFilterAction(word_data[\"action\"])\n                description = word_data.get(\"description\")\n\n                self.blocked_words[keyword] = (action, description)\n\n            verbose_proxy_logger.info(\"Loaded %s blocked words from %s\", len(data[\"blocked_words\"]), file_path)\n        except FileNotFoundError:\n            raise FileNotFoundError(f\"Blocked words file not found: {file_path}\")\n        except Exception as e:\n            raise Exception(f\"Error loading blocked words file {file_path}: {e}\")\n\n    def _find_pattern_spans(self, text: str, pattern_entry: CompiledPatternEntry) -> list[tuple[int, int]]:\n        \"\"\"Return all match spans for a pattern, applying contextual rules if required.\"\"\"\n\n        regex: Final[Pattern[str]] = pattern_entry[\"regex\"]\n        keyword_regex: Final[Pattern[str] | None] = pattern_entry.get(\"keyword_regex\")\n        allow_word_numbers: Final[bool] = pattern_entry.get(\"allow_word_numbers\", False)\n\n        keyword_matches: Final = list(keyword_regex.finditer(text)) if keyword_regex is not None else None\n        if keyword_matches is not None and not keyword_matches:\n            return []\n\n        match_spans: Final[list[tuple[int, int]]] = []\n\n        for match in regex.finditer(text):\n            if keyword_matches is not None and not self._match_near_keyword(\n                match.start(), match.end(), keyword_matches, text\n            ):","sourceCodeStart":794,"sourceCodeEnd":830,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py#L794-L830","documentation":"Generic Exception wrapping any non-FileNotFoundError failure while loading the blocked-words file. The wrapped cause (after the colon) is the real problem: yaml.YAMLError for malformed YAML syntax, PermissionError for unreadable files, ValueError from ContentFilterAction(...) for an invalid action value (only BLOCK/MASK-style enum members are accepted), or AttributeError when keyword is not a string and .lower() fails.","triggerScenarios":"The blocked-words file exists and parses to a dict with blocked_words, but something inside fails: YAML syntax error (tabs, bad indentation), an action like 'block'/'censor' that is not a valid enum value, keyword as a number/None, or restrictive file permissions.","commonSituations":"Tabs instead of spaces in hand-edited YAML; lowercase action values copied from docs of other tools; a keyword field containing a number (e.g. 42) instead of a quoted string; file owned by root while the proxy runs as a non-root user.","solutions":["Read the exception text after the colon — it names the exact underlying error class and message.","Validate the YAML parses: python -c \"import yaml; yaml.safe_load(open('blocked_words.yaml'))\".","Make every action exactly a valid ContentFilterAction value (e.g. 'BLOCK', 'MASK') and every keyword a quoted string.","Check file permissions so the proxy's runtime user can read the file."],"exampleFix":"# before\nblocked_words:\n  - keyword: 42          # unquoted number -> .lower() fails\n    action: block         # wrong value for the action enum\n\n# after\nblocked_words:\n  - keyword: \"42\"\n    action: BLOCK","handlingStrategy":"validation","validationCode":"# Dry-run the full load: schema, enum actions, string keywords, permissions\nimport yaml\nfrom litellm.types.proxy.guardrails.litellm_content_filter import ContentFilterAction\n\ndef dry_run_blocked_words(path: str) -> None:\n    with open(path) as f:                      # raises PermissionError early\n        data = yaml.safe_load(f)               # raises YAMLError early\n    assert isinstance(data, dict) and \"blocked_words\" in data\n    for w in data[\"blocked_words\"]:\n        assert isinstance(w[\"keyword\"], str), f\"keyword must be a string: {w}\"\n        ContentFilterAction(w[\"action\"])      # raises ValueError for bad actions","typeGuard":"def blocked_words_entry_ok(entry: object) -> bool:\n    \"\"\"Narrow one parsed entry to the loadable shape (string keyword + valid action).\"\"\"\n    if not isinstance(entry, dict):\n        return False\n    kw, act = entry.get(\"keyword\"), entry.get(\"action\")\n    return isinstance(kw, str) and bool(kw) and isinstance(act, str) and act in (\"BLOCK\", \"MASK\")","tryCatchPattern":null,"preventionTips":["Quote every keyword in YAML — unquoted numbers/booleans make .lower() fail downstream.","Use exactly BLOCK / MASK for action values; lowercase variants are rejected by the enum.","Validate the file with the dry-run above in CI and after every edit.","Ensure the proxy runtime user can read the file (chmod/chown), especially when it's root-owned in a container."],"tags":["yaml","configuration","validation","content-filter"],"backgroundTag":"config-file-parse-error","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}