{
  "type": "guide",
  "id": "screen-tool-output-for-prompt-injection",
  "title": "Screen tool and web output for prompt injection before your agent reads it",
  "summary": "Put a check between untrusted tool results and the model: Llama Prompt Guard 2 for injection attempts, LlamaFirewall's scanners for hidden text and generated code, and Llama Guard for content safety. Includes a tested screening wrapper and the limits of each layer.",
  "author": "Agentica Author",
  "tags": [
    "prompt-injection",
    "security",
    "guardrails",
    "tool-use",
    "agents",
    "classifiers",
    "python",
    "llama"
  ],
  "published": "2026-11-03",
  "last_verified": "2026-11-03",
  "difficulty": "intermediate",
  "time_estimate": "40 min",
  "entries": [
    "llama-prompt-guard-2",
    "llamafirewall",
    "llama-guard",
    "hol-guard",
    "agentdojo"
  ],
  "links": {
    "html": "https://indexagentica.com/guides/screen-tool-output-for-prompt-injection/",
    "markdown": "https://indexagentica.com/guides/screen-tool-output-for-prompt-injection.md",
    "json": "https://indexagentica.com/api/longform/guides/screen-tool-output-for-prompt-injection.json",
    "source": "https://github.com/Drudley/indexagentica/blob/main/content-long/guides/screen-tool-output-for-prompt-injection.md"
  },
  "status": "published",
  "prerequisites": [
    "Python 3.10 or later (llamafirewall requires >=3.10)",
    "A Hugging Face account that has been granted access to the gated meta-llama/Llama-Prompt-Guard-2-22M or -86M repository, for the classifier",
    "An agent loop where you can intercept tool results before they go back to the model"
  ],
  "entries_detail": [
    {
      "id": "llama-prompt-guard-2",
      "name": "Llama Prompt Guard 2",
      "summary": "Meta's small open-weight classifiers (86M and 22M) that flag prompt injection and jailbreak attempts in user input and untrusted content.",
      "url": "https://indexagentica.com/entries/llama-prompt-guard-2/",
      "json": "https://indexagentica.com/api/entries/llama-prompt-guard-2.json"
    },
    {
      "id": "llamafirewall",
      "name": "LlamaFirewall",
      "summary": "Meta's open-source guardrail framework for agents that chains scanners for prompt injection, goal hijacking and insecure generated code.",
      "url": "https://indexagentica.com/entries/llamafirewall/",
      "json": "https://indexagentica.com/api/entries/llamafirewall.json"
    },
    {
      "id": "llama-guard",
      "name": "Llama Guard",
      "summary": "Meta's open-weight safety classifiers that label LLM prompts and responses as safe or unsafe against the MLCommons hazard taxonomy.",
      "url": "https://indexagentica.com/entries/llama-guard/",
      "json": "https://indexagentica.com/api/entries/llama-guard.json"
    },
    {
      "id": "hol-guard",
      "name": "HOL Guard",
      "summary": "Local-first security layer that reviews AI coding agents' shell commands, file access, package installs and MCP tool calls before they run.",
      "url": "https://indexagentica.com/entries/hol-guard/",
      "json": "https://indexagentica.com/api/entries/hol-guard.json"
    },
    {
      "id": "agentdojo",
      "name": "AgentDojo",
      "summary": "ETH Zurich benchmark environment for prompt-injection attacks and defenses on tool-using LLM agents, with suites like workspace, banking, travel and slack.",
      "url": "https://indexagentica.com/entries/agentdojo/",
      "json": "https://indexagentica.com/api/entries/agentdojo.json"
    }
  ],
  "related": [
    {
      "type": "guide",
      "id": "test-an-agent-for-prompt-injection",
      "title": "Test a tool-using agent for prompt injection with AgentDojo",
      "url": "https://indexagentica.com/guides/test-an-agent-for-prompt-injection/",
      "json": "https://indexagentica.com/api/longform/guides/test-an-agent-for-prompt-injection.json"
    },
    {
      "type": "comparison",
      "id": "agent-guardrail-frameworks",
      "title": "Runtime guardrails for agents: Guardrails AI, NeMo Guardrails, Invariant and HOL Guard",
      "url": "https://indexagentica.com/compare/agent-guardrail-frameworks/",
      "json": "https://indexagentica.com/api/longform/compare/agent-guardrail-frameworks.json"
    },
    {
      "type": "comparison",
      "id": "llm-red-teaming-tools",
      "title": "Open-source red-teaming tools for LLM apps and agents",
      "url": "https://indexagentica.com/compare/llm-red-teaming-tools/",
      "json": "https://indexagentica.com/api/longform/compare/llm-red-teaming-tools.json"
    },
    {
      "type": "guide",
      "id": "run-untrusted-code-in-a-sandbox",
      "title": "Run agent-generated code safely in a sandbox",
      "url": "https://indexagentica.com/guides/run-untrusted-code-in-a-sandbox/",
      "json": "https://indexagentica.com/api/longform/guides/run-untrusted-code-in-a-sandbox.json"
    },
    {
      "type": "guide",
      "id": "scan-agent-written-code-with-semgrep-mcp",
      "title": "Scan agent-written code for vulnerabilities with the Semgrep MCP server",
      "url": "https://indexagentica.com/guides/scan-agent-written-code-with-semgrep-mcp/",
      "json": "https://indexagentica.com/api/longform/guides/scan-agent-written-code-with-semgrep-mcp.json"
    }
  ],
  "sources": [
    {
      "title": "Llama Prompt Guard 2 86M model card",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/86M/MODEL_CARD.md",
      "accessed": "2026-11-03"
    },
    {
      "title": "Llama Prompt Guard 2 22M model card",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/22M/MODEL_CARD.md",
      "accessed": "2026-11-03"
    },
    {
      "title": "Llama Prompt Guard 2 22M on Hugging Face (gated)",
      "url": "https://huggingface.co/meta-llama/Llama-Prompt-Guard-2-22M",
      "accessed": "2026-11-03"
    },
    {
      "title": "llama-cookbook: Prompt Guard inference utilities",
      "url": "https://github.com/meta-llama/llama-cookbook/blob/main/getting-started/responsible_ai/prompt_guard/inference.py",
      "accessed": "2026-11-03"
    },
    {
      "title": "LlamaFirewall README",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/README.md",
      "accessed": "2026-11-03"
    },
    {
      "title": "llamafirewall on PyPI",
      "url": "https://pypi.org/project/llamafirewall/",
      "accessed": "2026-11-03"
    },
    {
      "title": "LlamaFirewall source: firewall and default scanners",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/llamafirewall.py",
      "accessed": "2026-11-03"
    },
    {
      "title": "LlamaFirewall source: Prompt Guard utilities",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/promptguard_utils.py",
      "accessed": "2026-11-03"
    },
    {
      "title": "LlamaFirewall source: Prompt Guard scanner",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/prompt_guard_scanner.py",
      "accessed": "2026-11-03"
    },
    {
      "title": "LlamaFirewall source: regex scanner",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/regex_scanner.py",
      "accessed": "2026-11-03"
    },
    {
      "title": "LlamaFirewall source: AlignmentCheck scanner",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/experimental/alignmentcheck_scanner.py",
      "accessed": "2026-11-03"
    },
    {
      "title": "LlamaFirewall source: custom LLM check scanner (AlignmentCheck defaults)",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/custom_check_scanner.py",
      "accessed": "2026-11-03"
    },
    {
      "title": "Llama Guard 4 12B model card",
      "url": "https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Guard4/12B/MODEL_CARD.md",
      "accessed": "2026-11-03"
    },
    {
      "title": "HOL Guard README",
      "url": "https://github.com/hashgraph-online/hol-guard",
      "accessed": "2026-11-03"
    },
    {
      "title": "Unicode Character Database, Blocks.txt (Tags: E0000..E007F)",
      "url": "https://www.unicode.org/Public/UCD/latest/ucd/Blocks.txt",
      "accessed": "2026-11-03"
    }
  ],
  "front_matter": {
    "id": "screen-tool-output-for-prompt-injection",
    "type": "guide",
    "title": "Screen tool and web output for prompt injection before your agent reads it",
    "summary": "Put a check between untrusted tool results and the model: Llama Prompt Guard 2 for injection attempts, LlamaFirewall's scanners for hidden text and generated code, and Llama Guard for content safety. Includes a tested screening wrapper and the limits of each layer.",
    "description": "A practical guide to screening the output of web fetches, file reads, email and other tools before it enters an agent's context. It explains what Llama Prompt Guard 2 (22M and 86M) does and does not classify, why its 512-token window means long tool output must be split, and gives a Python screener that loads the model from local files only, scores every 512-token window, flags Unicode tag characters, fails closed on oversized input and never logs the screened text. It covers LlamaFirewall 1.0.3's PromptGuard, HiddenASCII, Regex, CodeShield and AlignmentCheck scanners and the gotchas we hit running it, where Llama Guard 4 fits, and HOL Guard as a harness-level option. Tested on 2026-11-03 on CPU. The Prompt Guard 2 weights are gated on Hugging Face and were not downloaded, so the screener was tested with a stand-in model; detection quality figures come from Meta's model card.",
    "author": "Agentica Author",
    "difficulty": "intermediate",
    "time_estimate": "40 min",
    "prerequisites": [
      "Python 3.10 or later (llamafirewall requires >=3.10)",
      "A Hugging Face account that has been granted access to the gated meta-llama/Llama-Prompt-Guard-2-22M or -86M repository, for the classifier",
      "An agent loop where you can intercept tool results before they go back to the model"
    ],
    "tags": [
      "prompt-injection",
      "security",
      "guardrails",
      "tool-use",
      "agents",
      "classifiers",
      "python",
      "llama"
    ],
    "entries": [
      "llama-prompt-guard-2",
      "llamafirewall",
      "llama-guard",
      "hol-guard",
      "agentdojo"
    ],
    "related": [
      "test-an-agent-for-prompt-injection",
      "agent-guardrail-frameworks",
      "llm-red-teaming-tools",
      "run-untrusted-code-in-a-sandbox",
      "scan-agent-written-code-with-semgrep-mcp"
    ],
    "sources": [
      {
        "title": "Llama Prompt Guard 2 86M model card",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/86M/MODEL_CARD.md",
        "accessed": "2026-11-03"
      },
      {
        "title": "Llama Prompt Guard 2 22M model card",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/22M/MODEL_CARD.md",
        "accessed": "2026-11-03"
      },
      {
        "title": "Llama Prompt Guard 2 22M on Hugging Face (gated)",
        "url": "https://huggingface.co/meta-llama/Llama-Prompt-Guard-2-22M",
        "accessed": "2026-11-03"
      },
      {
        "title": "llama-cookbook: Prompt Guard inference utilities",
        "url": "https://github.com/meta-llama/llama-cookbook/blob/main/getting-started/responsible_ai/prompt_guard/inference.py",
        "accessed": "2026-11-03"
      },
      {
        "title": "LlamaFirewall README",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/README.md",
        "accessed": "2026-11-03"
      },
      {
        "title": "llamafirewall on PyPI",
        "url": "https://pypi.org/project/llamafirewall/",
        "accessed": "2026-11-03"
      },
      {
        "title": "LlamaFirewall source: firewall and default scanners",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/llamafirewall.py",
        "accessed": "2026-11-03"
      },
      {
        "title": "LlamaFirewall source: Prompt Guard utilities",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/promptguard_utils.py",
        "accessed": "2026-11-03"
      },
      {
        "title": "LlamaFirewall source: Prompt Guard scanner",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/prompt_guard_scanner.py",
        "accessed": "2026-11-03"
      },
      {
        "title": "LlamaFirewall source: regex scanner",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/regex_scanner.py",
        "accessed": "2026-11-03"
      },
      {
        "title": "LlamaFirewall source: AlignmentCheck scanner",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/experimental/alignmentcheck_scanner.py",
        "accessed": "2026-11-03"
      },
      {
        "title": "LlamaFirewall source: custom LLM check scanner (AlignmentCheck defaults)",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/custom_check_scanner.py",
        "accessed": "2026-11-03"
      },
      {
        "title": "Llama Guard 4 12B model card",
        "url": "https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Guard4/12B/MODEL_CARD.md",
        "accessed": "2026-11-03"
      },
      {
        "title": "HOL Guard README",
        "url": "https://github.com/hashgraph-online/hol-guard",
        "accessed": "2026-11-03"
      },
      {
        "title": "Unicode Character Database, Blocks.txt (Tags: E0000..E007F)",
        "url": "https://www.unicode.org/Public/UCD/latest/ucd/Blocks.txt",
        "accessed": "2026-11-03"
      }
    ],
    "last_verified": "2026-11-03",
    "published": "2026-11-03"
  },
  "markdown": "\n## Why screen tool output\n\nAn agent that browses, reads email or opens files puts text written by strangers into its own context. The [Prompt Guard 2 model card](https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/86M/MODEL_CARD.md) describes the risk: prompt injections \"manipulate untrusted third-party and user data in the context window to make a model execute unintended instructions.\" This guide adds one check at that boundary. Each tool result is scored before the model sees it, and a flagged result is withheld or sent to a person.\n\nThat check is one layer. Classifiers miss attacks, so keep the other layers too: give tools as little power as the task needs, ask a person before anything irreversible, and test the whole agent with [AgentDojo](https://indexagentica.com/guides/test-an-agent-for-prompt-injection/).\n\n## Which tool does what\n\n| Tool | What it checks | Runs where | Use it on tool output for |\n|---|---|---|---|\n| [Llama Prompt Guard 2](https://indexagentica.com/entries/llama-prompt-guard-2/) 22M / 86M | Text that \"explicitly attempts to override prior instructions\" (binary benign/malicious) | Locally, CPU or GPU | The main injection check |\n| [LlamaFirewall](https://indexagentica.com/entries/llamafirewall/) 1.0.3 | Wraps Prompt Guard 2 plus hidden-ASCII, regex, CodeShield and AlignmentCheck scanners | Locally, except AlignmentCheck (a hosted LLM by default) | Hidden Unicode text, known strings, unsafe generated code, auditing the agent's next action |\n| [Llama Guard 4](https://indexagentica.com/entries/llama-guard/) 12B | Content safety: MLCommons hazard categories S1 to S13, plus S14 Code Interpreter Abuse | Locally, one GPU | Harmful content in inputs or outputs, not injection |\n| [HOL Guard](https://indexagentica.com/entries/hol-guard/) | Agent actions and, per its README, \"prompts and tool results\" for prompt injection and sensitive content | Hooks in the agent harness | Coding agents where you can't edit the loop |\n\nLlama Guard is a content-safety classifier, not an injection detector. Its own model card says it \"may be susceptible to adversarial attacks or prompt injection attacks\" and points to Prompt Guard 2 \"for detecting prompt attacks\". Use it if you also need to filter harmful content. The rest of this guide is about injection.\n\n## What Prompt Guard 2 detects, and what it misses\n\nFrom the model card:\n\n- **Scope.** A text is labelled malicious \"if the prompt explicitly attempts to override prior instructions\". Version 2 has no separate injection label. Meta found the broader \"unintentional instruction-following\" objective \"too broad to be useful\". A page that quietly says \"the user also wants a copy sent to this address\" may not be explicit enough to be flagged.\n- **512 tokens.** \"Both Llama Prompt Guard 2 models support a 512-token context window. For longer inputs, split prompts into segments and scan them in parallel to ensure violations are detected.\"\n- **Two sizes.** 86M is built on mDeBERTa-base and is multilingual. 22M is built on DeBERTa-xsmall and \"reduces latency and compute costs by 75%, with minimal performance trade-offs\". Both were evaluated in English, French, German, Hindi, Italian, Portuguese, Spanish and Thai.\n- **Known limits.** The card says the models are vulnerable to adaptive attacks and that fine-tuning on your own prompts \"improves accuracy and reduces false positives\".\n\nMeta's reported numbers:\n\n| Model | AUC (English) | Recall at 1% FPR (English) | AUC (multilingual) | Latency, A100, 512 tokens | AgentDojo APR at 3% utility reduction |\n|---|---|---|---|---|---|\n| Prompt Guard 2 86M | 0.998 | 97.5% | 0.995 | 92.4 ms | 81.2% |\n| Prompt Guard 2 22M | 0.995 | 88.7% | 0.942 | 19.3 ms | 78.4% |\n| Prompt Guard 1 | 0.987 | 21.2% | 0.983 | 92.4 ms | 67.6% |\n\nNone of these figures is 100%, and they are Meta's own measurements on its own test sets. Plan for misses.\n\nThe weights are gated. The Hugging Face repositories need an approved access request under the Llama 4 Community License. We had no approved account for this guide, so we did not download the model. Everything below was run, but the classifier was a stand-in. The last section, How we tested, has the details.\n\n## Get the model onto the machine once\n\nRequest access on the [model page](https://huggingface.co/meta-llama/Llama-Prompt-Guard-2-22M). After approval, download the files to a fixed directory, from a machine where you are logged in:\n\n```bash\nhf auth login\nhf download meta-llama/Llama-Prompt-Guard-2-22M --local-dir ./models/Llama-Prompt-Guard-2-22M\n```\n\nYour agent then loads from that directory and needs no Hugging Face token or network access at run time.\n\n## A screener for tool output\n\nThis module loads the classifier with `local_files_only=True`, so it can't download anything or prompt for a login. It splits the text into 512-token windows that overlap by 64 tokens, scores each window and keeps the highest score. The cookbook's [inference utilities](https://github.com/meta-llama/llama-cookbook/blob/main/getting-started/responsible_ai/prompt_guard/inference.py) also take the maximum over chunks. The module also counts characters from the Unicode Tags block, which render as nothing but can carry ASCII text. Input that needs more than `max_chunks` windows is flagged instead of being partly scored. The result holds numbers and flags only, never the text.\n\n```python\n\"\"\"Screen untrusted tool or web output with Llama Prompt Guard 2 before an agent reads it.\n\nLoads the model from local files only: it never downloads and never asks for a\nHugging Face login. Results never include the screened text.\n\"\"\"\nimport unicodedata\n\nimport torch\nfrom transformers import AutoModelForSequenceClassification, AutoTokenizer\n\nTAG_CHARS = range(0xE0000, 0xE0080)  # Unicode Tags block: invisible \"hidden ASCII\"\n\n\nclass Screener:\n    def __init__(self, model_path, threshold=0.9, window=512, overlap=64, max_chunks=64):\n        self.tok = AutoTokenizer.from_pretrained(model_path, local_files_only=True)\n        self.model = AutoModelForSequenceClassification.from_pretrained(\n            model_path, local_files_only=True).eval()\n        labels = {v.upper(): int(k) for k, v in self.model.config.id2label.items()}\n        if \"MALICIOUS\" not in labels:\n            raise ValueError(f\"no MALICIOUS label in {self.model.config.id2label}\")\n        self.bad = labels[\"MALICIOUS\"]\n        self.threshold = threshold\n        self.window = window\n        self.body = window - len(self.tok(\"\")[\"input_ids\"])  # leave room for [CLS]/[SEP]\n        self.step = self.body - overlap\n        self.max_chunks = max_chunks\n\n    def chunks(self, text):\n        ids = self.tok(text, add_special_tokens=False)[\"input_ids\"]\n        starts = range(0, max(len(ids) - self.body, 0) + self.step, self.step)\n        return [ids[s:s + self.body] for s in starts] or [[]]\n\n    @torch.no_grad()\n    def score(self, text, batch=16):\n        best = 0.0\n        parts = self.chunks(text)\n        for i in range(0, len(parts), batch):\n            texts = [self.tok.decode(p) for p in parts[i:i + batch]]\n            enc = self.tok(texts, truncation=True, max_length=self.window,\n                           padding=True, return_tensors=\"pt\")\n            probs = torch.softmax(self.model(**enc).logits, dim=-1)[:, self.bad]\n            best = max(best, probs.max().item())\n        return best, len(parts)\n\n    def screen(self, text):\n        hidden = sum(ord(c) in TAG_CHARS for c in text)\n        fmt = sum(unicodedata.category(c) == \"Cf\" for c in text) - hidden\n        n = len(self.chunks(text))\n        if n > self.max_chunks:  # fail closed rather than score part of it\n            return {\"flagged\": True, \"score\": None, \"chunks\": n, \"too_long\": True,\n                    \"tag_chars\": hidden, \"other_format_chars\": fmt}\n        score, n = self.score(text)\n        flagged = score >= self.threshold or hidden > 0\n        return {\"flagged\": flagged, \"score\": round(score, 4), \"chunks\": n,\n                \"too_long\": False, \"tag_chars\": hidden, \"other_format_chars\": fmt}\n```\n\nThe 0.9 threshold matches LlamaFirewall's default for its PromptGuard scanner. Tune it on a sample of your real tool output, because false positives are what make people switch a guard off. `other_format_chars` (zero-width spaces and similar) is reported but not blocked, since ordinary pages contain them.\n\nWrap every tool that returns outside text:\n\n```python\nimport logging\nfrom screen_tool_output import Screener\n\nlog = logging.getLogger(\"screen\")\nscreener = Screener(\"./models/Llama-Prompt-Guard-2-22M\")\n\ndef guarded(tool_name, output):\n    verdict = screener.screen(output)\n    log.info(\"screened %s: %s\", tool_name, verdict)  # the verdict holds no tool text\n    if verdict[\"flagged\"]:\n        return f\"[{tool_name} output withheld: flagged as possible prompt injection, score {verdict['score']}]\"\n    return output\n```\n\nReturn the placeholder to the model so it knows the call happened. Keep the original in a quarantine store that a person can review. Don't put it in the same log as the verdict.\n\n## LlamaFirewall: what works on tool output\n\nLlamaFirewall assigns scanners per message role. `ToolMessage` has the role `Role.TOOL`. With no configuration, tool messages get CodeShield and PromptGuard. When several scanners run, any BLOCK wins.\n\nThis configuration ran on CPU with no gated model and no API key:\n\n```python\nfrom llamafirewall import LlamaFirewall, Role, ScanDecision, ScannerType, ToolMessage\n\nfw = LlamaFirewall({Role.TOOL: [ScannerType.HIDDEN_ASCII, ScannerType.REGEX]})\nresult = fw.scan(ToolMessage(content=tool_output))\nif result.decision != ScanDecision.ALLOW:\n    ...  # withhold or ask a person\n```\n\nWhat we saw with `llamafirewall` 1.0.3:\n\n- Text hidden in tag characters was blocked, and the reason contained the decoded hidden text.\n- \"IGNORE PREVIOUS INSTRUCTIONS\" was blocked by the default regex. \"Disregard what you were told earlier\" passed. The default injection pattern is just `ignore previous instructions|ignore all instructions`.\n- The default regex set also blocks email addresses, phone numbers, card numbers and US social security numbers. That blocked a tool result that only contained a contact address. On tool output you will usually want your own patterns.\n- CodeShield blocked a snippet using MD5 for passwords (CWE-327). That makes sense for code an agent is about to run or commit. See also [scanning agent-written code with Semgrep](https://indexagentica.com/guides/scan-agent-written-code-with-semgrep-mcp/).\n- A role with no scanners configured returns ALLOW with the reason \"default\". If you configure only `Role.USER`, tool output passes unchecked.\n\nGotchas with `ScannerType.PROMPT_GUARD` in 1.0.3, the latest release on PyPI (May 2025):\n\n- **Imports fail with current huggingface_hub.** On a fresh install today, the scanner raised `ImportError: cannot import name 'HfFolder' from 'huggingface_hub'` with huggingface_hub 2.4.0. The repository's main branch has the fix, but it isn't released. Pin `\"huggingface_hub<1.0\"` (we used 0.36.2, with transformers 4.57.6) or use the screener above.\n- **It always uses the 86M model** (`meta-llama/Llama-Prompt-Guard-2-86M`), and with no saved token it calls Hugging Face's interactive `login()`. In a non-interactive process that ended in `EOFError`.\n- **It only scores the first 512 tokens.** The text is tokenized with `truncation=True, max_length=512`. An injection further down a long page is never seen. Split long output yourself, as in the screener, before you hand it to the scanner.\n- **Its BLOCK reason includes the full text** (`Full text: \"...\"`). Don't send `result.reason` to shared logs.\n\n**AlignmentCheck** is different. Instead of looking at the tool output, it asks an LLM whether the agent's latest action still serves the user's original request. That can catch injections a classifier misses. In 1.0.3 the firewall builds it with fixed defaults: `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` at `https://api.together.xyz/v1`, with the key in `TOGETHER_API_KEY`. It sends the whole trace, user request included, to that endpoint. Check that this is acceptable for your data before you turn it on. We did not run AlignmentCheck.\n\n## Put it together\n\n1. Mark which tools return outside content: web fetch, search, email, files, other agents and MCP servers you don't run yourself.\n2. Screen each result with the screener, plus LlamaFirewall's hidden-ASCII check or your own patterns. Withhold flagged results and keep them for review.\n3. Limit what a successful injection can do: read-only credentials, allowlisted destinations and a person approving sends, payments and deletes. The [guardrail frameworks comparison](https://indexagentica.com/compare/agent-guardrail-frameworks/) covers policy layers that check tool calls.\n4. For coding agents, [HOL Guard](https://indexagentica.com/entries/hol-guard/) works through the agent's own hooks and MCP proxies. Its README notes that coverage \"depends on the events each agent exposes\", so check its support matrix for your harness.\n5. Measure. Run [AgentDojo](https://indexagentica.com/guides/test-an-agent-for-prompt-injection/) with and without the screener, and add attacks from the [red-teaming tools](https://indexagentica.com/compare/llm-red-teaming-tools/). A guard you haven't measured is a guess.\n\n## How we tested\n\nRun on 2026-11-03 on CPU (Python 3.13.5, torch 2.15.0+cpu, llamafirewall 1.0.3, codeshield 1.0.1).\n\n- The Prompt Guard 2 repositories are gated (`\"gated\": \"manual\"` on the Hugging Face API), and an unauthenticated download of `config.json` returned 401. We had no approved account, so **we did not measure detection quality**. The figures above are Meta's.\n- The screener was run with transformers 4.57.6 and 5.22.1, against a stand-in model: the ungated `microsoft/deberta-v3-xsmall` checkpoint with an untrained two-label head. The tests checked these code paths:\n  - real tokenization and forward passes\n  - every token of a 2,000-token input covered by five windows of at most 512 tokens\n  - an injection placed after token 2,000 caught by a stub head, which first-512-token truncation missed\n  - tag characters flagged\n  - oversized input failing closed\n  - an uncached gated model id raising `OSError` at once, with no download or prompt\n  - the wrapper withholding a flagged result without echoing it\n- LlamaFirewall's HIDDEN_ASCII, REGEX and CODE_SHIELD scanners were run as shown. PROMPT_GUARD was run only far enough to see the import error and the login prompt.\n",
  "raw": "---\nid: screen-tool-output-for-prompt-injection\ntype: guide\ntitle: Screen tool and web output for prompt injection before your agent reads it\nsummary: \"Put a check between untrusted tool results and the model: Llama Prompt Guard 2 for injection attempts, LlamaFirewall's scanners for hidden text and generated code, and Llama Guard for content safety. Includes a tested screening wrapper and the limits of each layer.\"\ndescription: \"A practical guide to screening the output of web fetches, file reads, email and other tools before it enters an agent's context. It explains what Llama Prompt Guard 2 (22M and 86M) does and does not classify, why its 512-token window means long tool output must be split, and gives a Python screener that loads the model from local files only, scores every 512-token window, flags Unicode tag characters, fails closed on oversized input and never logs the screened text. It covers LlamaFirewall 1.0.3's PromptGuard, HiddenASCII, Regex, CodeShield and AlignmentCheck scanners and the gotchas we hit running it, where Llama Guard 4 fits, and HOL Guard as a harness-level option. Tested on 2026-11-03 on CPU. The Prompt Guard 2 weights are gated on Hugging Face and were not downloaded, so the screener was tested with a stand-in model; detection quality figures come from Meta's model card.\"\nauthor: Agentica Author\ndifficulty: intermediate\ntime_estimate: 40 min\nprerequisites:\n  - \"Python 3.10 or later (llamafirewall requires >=3.10)\"\n  - \"A Hugging Face account that has been granted access to the gated meta-llama/Llama-Prompt-Guard-2-22M or -86M repository, for the classifier\"\n  - \"An agent loop where you can intercept tool results before they go back to the model\"\ntags: [prompt-injection, security, guardrails, tool-use, agents, classifiers, python, llama]\nentries: [llama-prompt-guard-2, llamafirewall, llama-guard, hol-guard, agentdojo]\nrelated: [test-an-agent-for-prompt-injection, agent-guardrail-frameworks, llm-red-teaming-tools, run-untrusted-code-in-a-sandbox, scan-agent-written-code-with-semgrep-mcp]\nsources:\n  - title: Llama Prompt Guard 2 86M model card\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/86M/MODEL_CARD.md\n    accessed: 2026-11-03\n  - title: Llama Prompt Guard 2 22M model card\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/22M/MODEL_CARD.md\n    accessed: 2026-11-03\n  - title: Llama Prompt Guard 2 22M on Hugging Face (gated)\n    url: https://huggingface.co/meta-llama/Llama-Prompt-Guard-2-22M\n    accessed: 2026-11-03\n  - title: \"llama-cookbook: Prompt Guard inference utilities\"\n    url: https://github.com/meta-llama/llama-cookbook/blob/main/getting-started/responsible_ai/prompt_guard/inference.py\n    accessed: 2026-11-03\n  - title: LlamaFirewall README\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/README.md\n    accessed: 2026-11-03\n  - title: llamafirewall on PyPI\n    url: https://pypi.org/project/llamafirewall/\n    accessed: 2026-11-03\n  - title: \"LlamaFirewall source: firewall and default scanners\"\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/llamafirewall.py\n    accessed: 2026-11-03\n  - title: \"LlamaFirewall source: Prompt Guard utilities\"\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/promptguard_utils.py\n    accessed: 2026-11-03\n  - title: \"LlamaFirewall source: Prompt Guard scanner\"\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/prompt_guard_scanner.py\n    accessed: 2026-11-03\n  - title: \"LlamaFirewall source: regex scanner\"\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/regex_scanner.py\n    accessed: 2026-11-03\n  - title: \"LlamaFirewall source: AlignmentCheck scanner\"\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/experimental/alignmentcheck_scanner.py\n    accessed: 2026-11-03\n  - title: \"LlamaFirewall source: custom LLM check scanner (AlignmentCheck defaults)\"\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/LlamaFirewall/src/llamafirewall/scanners/custom_check_scanner.py\n    accessed: 2026-11-03\n  - title: Llama Guard 4 12B model card\n    url: https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Guard4/12B/MODEL_CARD.md\n    accessed: 2026-11-03\n  - title: HOL Guard README\n    url: https://github.com/hashgraph-online/hol-guard\n    accessed: 2026-11-03\n  - title: \"Unicode Character Database, Blocks.txt (Tags: E0000..E007F)\"\n    url: https://www.unicode.org/Public/UCD/latest/ucd/Blocks.txt\n    accessed: 2026-11-03\nlast_verified: 2026-11-03\npublished: 2026-11-03\n---\n\n## Why screen tool output\n\nAn agent that browses, reads email or opens files puts text written by strangers into its own context. The [Prompt Guard 2 model card](https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/86M/MODEL_CARD.md) describes the risk: prompt injections \"manipulate untrusted third-party and user data in the context window to make a model execute unintended instructions.\" This guide adds one check at that boundary. Each tool result is scored before the model sees it, and a flagged result is withheld or sent to a person.\n\nThat check is one layer. Classifiers miss attacks, so keep the other layers too: give tools as little power as the task needs, ask a person before anything irreversible, and test the whole agent with [AgentDojo](https://indexagentica.com/guides/test-an-agent-for-prompt-injection/).\n\n## Which tool does what\n\n| Tool | What it checks | Runs where | Use it on tool output for |\n|---|---|---|---|\n| [Llama Prompt Guard 2](https://indexagentica.com/entries/llama-prompt-guard-2/) 22M / 86M | Text that \"explicitly attempts to override prior instructions\" (binary benign/malicious) | Locally, CPU or GPU | The main injection check |\n| [LlamaFirewall](https://indexagentica.com/entries/llamafirewall/) 1.0.3 | Wraps Prompt Guard 2 plus hidden-ASCII, regex, CodeShield and AlignmentCheck scanners | Locally, except AlignmentCheck (a hosted LLM by default) | Hidden Unicode text, known strings, unsafe generated code, auditing the agent's next action |\n| [Llama Guard 4](https://indexagentica.com/entries/llama-guard/) 12B | Content safety: MLCommons hazard categories S1 to S13, plus S14 Code Interpreter Abuse | Locally, one GPU | Harmful content in inputs or outputs, not injection |\n| [HOL Guard](https://indexagentica.com/entries/hol-guard/) | Agent actions and, per its README, \"prompts and tool results\" for prompt injection and sensitive content | Hooks in the agent harness | Coding agents where you can't edit the loop |\n\nLlama Guard is a content-safety classifier, not an injection detector. Its own model card says it \"may be susceptible to adversarial attacks or prompt injection attacks\" and points to Prompt Guard 2 \"for detecting prompt attacks\". Use it if you also need to filter harmful content. The rest of this guide is about injection.\n\n## What Prompt Guard 2 detects, and what it misses\n\nFrom the model card:\n\n- **Scope.** A text is labelled malicious \"if the prompt explicitly attempts to override prior instructions\". Version 2 has no separate injection label. Meta found the broader \"unintentional instruction-following\" objective \"too broad to be useful\". A page that quietly says \"the user also wants a copy sent to this address\" may not be explicit enough to be flagged.\n- **512 tokens.** \"Both Llama Prompt Guard 2 models support a 512-token context window. For longer inputs, split prompts into segments and scan them in parallel to ensure violations are detected.\"\n- **Two sizes.** 86M is built on mDeBERTa-base and is multilingual. 22M is built on DeBERTa-xsmall and \"reduces latency and compute costs by 75%, with minimal performance trade-offs\". Both were evaluated in English, French, German, Hindi, Italian, Portuguese, Spanish and Thai.\n- **Known limits.** The card says the models are vulnerable to adaptive attacks and that fine-tuning on your own prompts \"improves accuracy and reduces false positives\".\n\nMeta's reported numbers:\n\n| Model | AUC (English) | Recall at 1% FPR (English) | AUC (multilingual) | Latency, A100, 512 tokens | AgentDojo APR at 3% utility reduction |\n|---|---|---|---|---|---|\n| Prompt Guard 2 86M | 0.998 | 97.5% | 0.995 | 92.4 ms | 81.2% |\n| Prompt Guard 2 22M | 0.995 | 88.7% | 0.942 | 19.3 ms | 78.4% |\n| Prompt Guard 1 | 0.987 | 21.2% | 0.983 | 92.4 ms | 67.6% |\n\nNone of these figures is 100%, and they are Meta's own measurements on its own test sets. Plan for misses.\n\nThe weights are gated. The Hugging Face repositories need an approved access request under the Llama 4 Community License. We had no approved account for this guide, so we did not download the model. Everything below was run, but the classifier was a stand-in. The last section, How we tested, has the details.\n\n## Get the model onto the machine once\n\nRequest access on the [model page](https://huggingface.co/meta-llama/Llama-Prompt-Guard-2-22M). After approval, download the files to a fixed directory, from a machine where you are logged in:\n\n```bash\nhf auth login\nhf download meta-llama/Llama-Prompt-Guard-2-22M --local-dir ./models/Llama-Prompt-Guard-2-22M\n```\n\nYour agent then loads from that directory and needs no Hugging Face token or network access at run time.\n\n## A screener for tool output\n\nThis module loads the classifier with `local_files_only=True`, so it can't download anything or prompt for a login. It splits the text into 512-token windows that overlap by 64 tokens, scores each window and keeps the highest score. The cookbook's [inference utilities](https://github.com/meta-llama/llama-cookbook/blob/main/getting-started/responsible_ai/prompt_guard/inference.py) also take the maximum over chunks. The module also counts characters from the Unicode Tags block, which render as nothing but can carry ASCII text. Input that needs more than `max_chunks` windows is flagged instead of being partly scored. The result holds numbers and flags only, never the text.\n\n```python\n\"\"\"Screen untrusted tool or web output with Llama Prompt Guard 2 before an agent reads it.\n\nLoads the model from local files only: it never downloads and never asks for a\nHugging Face login. Results never include the screened text.\n\"\"\"\nimport unicodedata\n\nimport torch\nfrom transformers import AutoModelForSequenceClassification, AutoTokenizer\n\nTAG_CHARS = range(0xE0000, 0xE0080)  # Unicode Tags block: invisible \"hidden ASCII\"\n\n\nclass Screener:\n    def __init__(self, model_path, threshold=0.9, window=512, overlap=64, max_chunks=64):\n        self.tok = AutoTokenizer.from_pretrained(model_path, local_files_only=True)\n        self.model = AutoModelForSequenceClassification.from_pretrained(\n            model_path, local_files_only=True).eval()\n        labels = {v.upper(): int(k) for k, v in self.model.config.id2label.items()}\n        if \"MALICIOUS\" not in labels:\n            raise ValueError(f\"no MALICIOUS label in {self.model.config.id2label}\")\n        self.bad = labels[\"MALICIOUS\"]\n        self.threshold = threshold\n        self.window = window\n        self.body = window - len(self.tok(\"\")[\"input_ids\"])  # leave room for [CLS]/[SEP]\n        self.step = self.body - overlap\n        self.max_chunks = max_chunks\n\n    def chunks(self, text):\n        ids = self.tok(text, add_special_tokens=False)[\"input_ids\"]\n        starts = range(0, max(len(ids) - self.body, 0) + self.step, self.step)\n        return [ids[s:s + self.body] for s in starts] or [[]]\n\n    @torch.no_grad()\n    def score(self, text, batch=16):\n        best = 0.0\n        parts = self.chunks(text)\n        for i in range(0, len(parts), batch):\n            texts = [self.tok.decode(p) for p in parts[i:i + batch]]\n            enc = self.tok(texts, truncation=True, max_length=self.window,\n                           padding=True, return_tensors=\"pt\")\n            probs = torch.softmax(self.model(**enc).logits, dim=-1)[:, self.bad]\n            best = max(best, probs.max().item())\n        return best, len(parts)\n\n    def screen(self, text):\n        hidden = sum(ord(c) in TAG_CHARS for c in text)\n        fmt = sum(unicodedata.category(c) == \"Cf\" for c in text) - hidden\n        n = len(self.chunks(text))\n        if n > self.max_chunks:  # fail closed rather than score part of it\n            return {\"flagged\": True, \"score\": None, \"chunks\": n, \"too_long\": True,\n                    \"tag_chars\": hidden, \"other_format_chars\": fmt}\n        score, n = self.score(text)\n        flagged = score >= self.threshold or hidden > 0\n        return {\"flagged\": flagged, \"score\": round(score, 4), \"chunks\": n,\n                \"too_long\": False, \"tag_chars\": hidden, \"other_format_chars\": fmt}\n```\n\nThe 0.9 threshold matches LlamaFirewall's default for its PromptGuard scanner. Tune it on a sample of your real tool output, because false positives are what make people switch a guard off. `other_format_chars` (zero-width spaces and similar) is reported but not blocked, since ordinary pages contain them.\n\nWrap every tool that returns outside text:\n\n```python\nimport logging\nfrom screen_tool_output import Screener\n\nlog = logging.getLogger(\"screen\")\nscreener = Screener(\"./models/Llama-Prompt-Guard-2-22M\")\n\ndef guarded(tool_name, output):\n    verdict = screener.screen(output)\n    log.info(\"screened %s: %s\", tool_name, verdict)  # the verdict holds no tool text\n    if verdict[\"flagged\"]:\n        return f\"[{tool_name} output withheld: flagged as possible prompt injection, score {verdict['score']}]\"\n    return output\n```\n\nReturn the placeholder to the model so it knows the call happened. Keep the original in a quarantine store that a person can review. Don't put it in the same log as the verdict.\n\n## LlamaFirewall: what works on tool output\n\nLlamaFirewall assigns scanners per message role. `ToolMessage` has the role `Role.TOOL`. With no configuration, tool messages get CodeShield and PromptGuard. When several scanners run, any BLOCK wins.\n\nThis configuration ran on CPU with no gated model and no API key:\n\n```python\nfrom llamafirewall import LlamaFirewall, Role, ScanDecision, ScannerType, ToolMessage\n\nfw = LlamaFirewall({Role.TOOL: [ScannerType.HIDDEN_ASCII, ScannerType.REGEX]})\nresult = fw.scan(ToolMessage(content=tool_output))\nif result.decision != ScanDecision.ALLOW:\n    ...  # withhold or ask a person\n```\n\nWhat we saw with `llamafirewall` 1.0.3:\n\n- Text hidden in tag characters was blocked, and the reason contained the decoded hidden text.\n- \"IGNORE PREVIOUS INSTRUCTIONS\" was blocked by the default regex. \"Disregard what you were told earlier\" passed. The default injection pattern is just `ignore previous instructions|ignore all instructions`.\n- The default regex set also blocks email addresses, phone numbers, card numbers and US social security numbers. That blocked a tool result that only contained a contact address. On tool output you will usually want your own patterns.\n- CodeShield blocked a snippet using MD5 for passwords (CWE-327). That makes sense for code an agent is about to run or commit. See also [scanning agent-written code with Semgrep](https://indexagentica.com/guides/scan-agent-written-code-with-semgrep-mcp/).\n- A role with no scanners configured returns ALLOW with the reason \"default\". If you configure only `Role.USER`, tool output passes unchecked.\n\nGotchas with `ScannerType.PROMPT_GUARD` in 1.0.3, the latest release on PyPI (May 2025):\n\n- **Imports fail with current huggingface_hub.** On a fresh install today, the scanner raised `ImportError: cannot import name 'HfFolder' from 'huggingface_hub'` with huggingface_hub 2.4.0. The repository's main branch has the fix, but it isn't released. Pin `\"huggingface_hub<1.0\"` (we used 0.36.2, with transformers 4.57.6) or use the screener above.\n- **It always uses the 86M model** (`meta-llama/Llama-Prompt-Guard-2-86M`), and with no saved token it calls Hugging Face's interactive `login()`. In a non-interactive process that ended in `EOFError`.\n- **It only scores the first 512 tokens.** The text is tokenized with `truncation=True, max_length=512`. An injection further down a long page is never seen. Split long output yourself, as in the screener, before you hand it to the scanner.\n- **Its BLOCK reason includes the full text** (`Full text: \"...\"`). Don't send `result.reason` to shared logs.\n\n**AlignmentCheck** is different. Instead of looking at the tool output, it asks an LLM whether the agent's latest action still serves the user's original request. That can catch injections a classifier misses. In 1.0.3 the firewall builds it with fixed defaults: `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` at `https://api.together.xyz/v1`, with the key in `TOGETHER_API_KEY`. It sends the whole trace, user request included, to that endpoint. Check that this is acceptable for your data before you turn it on. We did not run AlignmentCheck.\n\n## Put it together\n\n1. Mark which tools return outside content: web fetch, search, email, files, other agents and MCP servers you don't run yourself.\n2. Screen each result with the screener, plus LlamaFirewall's hidden-ASCII check or your own patterns. Withhold flagged results and keep them for review.\n3. Limit what a successful injection can do: read-only credentials, allowlisted destinations and a person approving sends, payments and deletes. The [guardrail frameworks comparison](https://indexagentica.com/compare/agent-guardrail-frameworks/) covers policy layers that check tool calls.\n4. For coding agents, [HOL Guard](https://indexagentica.com/entries/hol-guard/) works through the agent's own hooks and MCP proxies. Its README notes that coverage \"depends on the events each agent exposes\", so check its support matrix for your harness.\n5. Measure. Run [AgentDojo](https://indexagentica.com/guides/test-an-agent-for-prompt-injection/) with and without the screener, and add attacks from the [red-teaming tools](https://indexagentica.com/compare/llm-red-teaming-tools/). A guard you haven't measured is a guess.\n\n## How we tested\n\nRun on 2026-11-03 on CPU (Python 3.13.5, torch 2.15.0+cpu, llamafirewall 1.0.3, codeshield 1.0.1).\n\n- The Prompt Guard 2 repositories are gated (`\"gated\": \"manual\"` on the Hugging Face API), and an unauthenticated download of `config.json` returned 401. We had no approved account, so **we did not measure detection quality**. The figures above are Meta's.\n- The screener was run with transformers 4.57.6 and 5.22.1, against a stand-in model: the ungated `microsoft/deberta-v3-xsmall` checkpoint with an untrained two-label head. The tests checked these code paths:\n  - real tokenization and forward passes\n  - every token of a 2,000-token input covered by five windows of at most 512 tokens\n  - an injection placed after token 2,000 caught by a stub head, which first-512-token truncation missed\n  - tag characters flagged\n  - oversized input failing closed\n  - an uncached gated model id raising `OSError` at once, with no download or prompt\n  - the wrapper withholding a flagged result without echoing it\n- LlamaFirewall's HIDDEN_ASCII, REGEX and CODE_SHIELD scanners were run as shown. PROMPT_GUARD was run only far enough to see the import error and the login prompt.\n",
  "html": "<h2>Why screen tool output</h2>\n<p>An agent that browses, reads email or opens files puts text written by strangers into its own context. The <a href=\"https://github.com/meta-llama/PurpleLlama/blob/main/Llama-Prompt-Guard-2/86M/MODEL_CARD.md\">Prompt Guard 2 model card</a> describes the risk: prompt injections &quot;manipulate untrusted third-party and user data in the context window to make a model execute unintended instructions.&quot; This guide adds one check at that boundary. Each tool result is scored before the model sees it, and a flagged result is withheld or sent to a person.</p>\n<p>That check is one layer. Classifiers miss attacks, so keep the other layers too: give tools as little power as the task needs, ask a person before anything irreversible, and test the whole agent with <a href=\"/guides/test-an-agent-for-prompt-injection/\">AgentDojo</a>.</p>\n<h2>Which tool does what</h2>\n<table><thead><tr><th scope=\"col\">Tool</th><th scope=\"col\">What it checks</th><th scope=\"col\">Runs where</th><th scope=\"col\">Use it on tool output for</th></tr></thead><tbody><tr><td><a href=\"/entries/llama-prompt-guard-2/\">Llama Prompt Guard 2</a> 22M / 86M</td><td>Text that &quot;explicitly attempts to override prior instructions&quot; (binary benign/malicious)</td><td>Locally, CPU or GPU</td><td>The main injection check</td></tr><tr><td><a href=\"/entries/llamafirewall/\">LlamaFirewall</a> 1.0.3</td><td>Wraps Prompt Guard 2 plus hidden-ASCII, regex, CodeShield and AlignmentCheck scanners</td><td>Locally, except AlignmentCheck (a hosted LLM by default)</td><td>Hidden Unicode text, known strings, unsafe generated code, auditing the agent&#39;s next action</td></tr><tr><td><a href=\"/entries/llama-guard/\">Llama Guard 4</a> 12B</td><td>Content safety: MLCommons hazard categories S1 to S13, plus S14 Code Interpreter Abuse</td><td>Locally, one GPU</td><td>Harmful content in inputs or outputs, not injection</td></tr><tr><td><a href=\"/entries/hol-guard/\">HOL Guard</a></td><td>Agent actions and, per its README, &quot;prompts and tool results&quot; for prompt injection and sensitive content</td><td>Hooks in the agent harness</td><td>Coding agents where you can&#39;t edit the loop</td></tr></tbody></table>\n<p>Llama Guard is a content-safety classifier, not an injection detector. Its own model card says it &quot;may be susceptible to adversarial attacks or prompt injection attacks&quot; and points to Prompt Guard 2 &quot;for detecting prompt attacks&quot;. Use it if you also need to filter harmful content. The rest of this guide is about injection.</p>\n<h2>What Prompt Guard 2 detects, and what it misses</h2>\n<p>From the model card:</p>\n<ul><li><strong>Scope.</strong> A text is labelled malicious &quot;if the prompt explicitly attempts to override prior instructions&quot;. Version 2 has no separate injection label. Meta found the broader &quot;unintentional instruction-following&quot; objective &quot;too broad to be useful&quot;. A page that quietly says &quot;the user also wants a copy sent to this address&quot; may not be explicit enough to be flagged.</li><li><strong>512 tokens.</strong> &quot;Both Llama Prompt Guard 2 models support a 512-token context window. For longer inputs, split prompts into segments and scan them in parallel to ensure violations are detected.&quot;</li><li><strong>Two sizes.</strong> 86M is built on mDeBERTa-base and is multilingual. 22M is built on DeBERTa-xsmall and &quot;reduces latency and compute costs by 75%, with minimal performance trade-offs&quot;. Both were evaluated in English, French, German, Hindi, Italian, Portuguese, Spanish and Thai.</li><li><strong>Known limits.</strong> The card says the models are vulnerable to adaptive attacks and that fine-tuning on your own prompts &quot;improves accuracy and reduces false positives&quot;.</li></ul>\n<p>Meta&#39;s reported numbers:</p>\n<table><thead><tr><th scope=\"col\">Model</th><th scope=\"col\">AUC (English)</th><th scope=\"col\">Recall at 1% FPR (English)</th><th scope=\"col\">AUC (multilingual)</th><th scope=\"col\">Latency, A100, 512 tokens</th><th scope=\"col\">AgentDojo APR at 3% utility reduction</th></tr></thead><tbody><tr><td>Prompt Guard 2 86M</td><td>0.998</td><td>97.5%</td><td>0.995</td><td>92.4 ms</td><td>81.2%</td></tr><tr><td>Prompt Guard 2 22M</td><td>0.995</td><td>88.7%</td><td>0.942</td><td>19.3 ms</td><td>78.4%</td></tr><tr><td>Prompt Guard 1</td><td>0.987</td><td>21.2%</td><td>0.983</td><td>92.4 ms</td><td>67.6%</td></tr></tbody></table>\n<p>None of these figures is 100%, and they are Meta&#39;s own measurements on its own test sets. Plan for misses.</p>\n<p>The weights are gated. The Hugging Face repositories need an approved access request under the Llama 4 Community License. We had no approved account for this guide, so we did not download the model. Everything below was run, but the classifier was a stand-in. The last section, How we tested, has the details.</p>\n<h2>Get the model onto the machine once</h2>\n<p>Request access on the <a href=\"https://huggingface.co/meta-llama/Llama-Prompt-Guard-2-22M\">model page</a>. After approval, download the files to a fixed directory, from a machine where you are logged in:</p>\n<pre><code class=\"language-bash\">hf auth login\nhf download meta-llama/Llama-Prompt-Guard-2-22M --local-dir ./models/Llama-Prompt-Guard-2-22M</code></pre>\n<p>Your agent then loads from that directory and needs no Hugging Face token or network access at run time.</p>\n<h2>A screener for tool output</h2>\n<p>This module loads the classifier with <code>local_files_only=True</code>, so it can&#39;t download anything or prompt for a login. It splits the text into 512-token windows that overlap by 64 tokens, scores each window and keeps the highest score. The cookbook&#39;s <a href=\"https://github.com/meta-llama/llama-cookbook/blob/main/getting-started/responsible_ai/prompt_guard/inference.py\">inference utilities</a> also take the maximum over chunks. The module also counts characters from the Unicode Tags block, which render as nothing but can carry ASCII text. Input that needs more than <code>max_chunks</code> windows is flagged instead of being partly scored. The result holds numbers and flags only, never the text.</p>\n<pre><code class=\"language-python\">&quot;&quot;&quot;Screen untrusted tool or web output with Llama Prompt Guard 2 before an agent reads it.\n\nLoads the model from local files only: it never downloads and never asks for a\nHugging Face login. Results never include the screened text.\n&quot;&quot;&quot;\nimport unicodedata\n\nimport torch\nfrom transformers import AutoModelForSequenceClassification, AutoTokenizer\n\nTAG_CHARS = range(0xE0000, 0xE0080)  # Unicode Tags block: invisible &quot;hidden ASCII&quot;\n\n\nclass Screener:\n    def __init__(self, model_path, threshold=0.9, window=512, overlap=64, max_chunks=64):\n        self.tok = AutoTokenizer.from_pretrained(model_path, local_files_only=True)\n        self.model = AutoModelForSequenceClassification.from_pretrained(\n            model_path, local_files_only=True).eval()\n        labels = {v.upper(): int(k) for k, v in self.model.config.id2label.items()}\n        if &quot;MALICIOUS&quot; not in labels:\n            raise ValueError(f&quot;no MALICIOUS label in {self.model.config.id2label}&quot;)\n        self.bad = labels[&quot;MALICIOUS&quot;]\n        self.threshold = threshold\n        self.window = window\n        self.body = window - len(self.tok(&quot;&quot;)[&quot;input_ids&quot;])  # leave room for [CLS]/[SEP]\n        self.step = self.body - overlap\n        self.max_chunks = max_chunks\n\n    def chunks(self, text):\n        ids = self.tok(text, add_special_tokens=False)[&quot;input_ids&quot;]\n        starts = range(0, max(len(ids) - self.body, 0) + self.step, self.step)\n        return [ids[s:s + self.body] for s in starts] or [[]]\n\n    @torch.no_grad()\n    def score(self, text, batch=16):\n        best = 0.0\n        parts = self.chunks(text)\n        for i in range(0, len(parts), batch):\n            texts = [self.tok.decode(p) for p in parts[i:i + batch]]\n            enc = self.tok(texts, truncation=True, max_length=self.window,\n                           padding=True, return_tensors=&quot;pt&quot;)\n            probs = torch.softmax(self.model(**enc).logits, dim=-1)[:, self.bad]\n            best = max(best, probs.max().item())\n        return best, len(parts)\n\n    def screen(self, text):\n        hidden = sum(ord(c) in TAG_CHARS for c in text)\n        fmt = sum(unicodedata.category(c) == &quot;Cf&quot; for c in text) - hidden\n        n = len(self.chunks(text))\n        if n &gt; self.max_chunks:  # fail closed rather than score part of it\n            return {&quot;flagged&quot;: True, &quot;score&quot;: None, &quot;chunks&quot;: n, &quot;too_long&quot;: True,\n                    &quot;tag_chars&quot;: hidden, &quot;other_format_chars&quot;: fmt}\n        score, n = self.score(text)\n        flagged = score &gt;= self.threshold or hidden &gt; 0\n        return {&quot;flagged&quot;: flagged, &quot;score&quot;: round(score, 4), &quot;chunks&quot;: n,\n                &quot;too_long&quot;: False, &quot;tag_chars&quot;: hidden, &quot;other_format_chars&quot;: fmt}</code></pre>\n<p>The 0.9 threshold matches LlamaFirewall&#39;s default for its PromptGuard scanner. Tune it on a sample of your real tool output, because false positives are what make people switch a guard off. <code>other_format_chars</code> (zero-width spaces and similar) is reported but not blocked, since ordinary pages contain them.</p>\n<p>Wrap every tool that returns outside text:</p>\n<pre><code class=\"language-python\">import logging\nfrom screen_tool_output import Screener\n\nlog = logging.getLogger(&quot;screen&quot;)\nscreener = Screener(&quot;./models/Llama-Prompt-Guard-2-22M&quot;)\n\ndef guarded(tool_name, output):\n    verdict = screener.screen(output)\n    log.info(&quot;screened %s: %s&quot;, tool_name, verdict)  # the verdict holds no tool text\n    if verdict[&quot;flagged&quot;]:\n        return f&quot;[{tool_name} output withheld: flagged as possible prompt injection, score {verdict[&#39;score&#39;]}]&quot;\n    return output</code></pre>\n<p>Return the placeholder to the model so it knows the call happened. Keep the original in a quarantine store that a person can review. Don&#39;t put it in the same log as the verdict.</p>\n<h2>LlamaFirewall: what works on tool output</h2>\n<p>LlamaFirewall assigns scanners per message role. <code>ToolMessage</code> has the role <code>Role.TOOL</code>. With no configuration, tool messages get CodeShield and PromptGuard. When several scanners run, any BLOCK wins.</p>\n<p>This configuration ran on CPU with no gated model and no API key:</p>\n<pre><code class=\"language-python\">from llamafirewall import LlamaFirewall, Role, ScanDecision, ScannerType, ToolMessage\n\nfw = LlamaFirewall({Role.TOOL: [ScannerType.HIDDEN_ASCII, ScannerType.REGEX]})\nresult = fw.scan(ToolMessage(content=tool_output))\nif result.decision != ScanDecision.ALLOW:\n    ...  # withhold or ask a person</code></pre>\n<p>What we saw with <code>llamafirewall</code> 1.0.3:</p>\n<ul><li>Text hidden in tag characters was blocked, and the reason contained the decoded hidden text.</li><li>&quot;IGNORE PREVIOUS INSTRUCTIONS&quot; was blocked by the default regex. &quot;Disregard what you were told earlier&quot; passed. The default injection pattern is just <code>ignore previous instructions|ignore all instructions</code>.</li><li>The default regex set also blocks email addresses, phone numbers, card numbers and US social security numbers. That blocked a tool result that only contained a contact address. On tool output you will usually want your own patterns.</li><li>CodeShield blocked a snippet using MD5 for passwords (CWE-327). That makes sense for code an agent is about to run or commit. See also <a href=\"/guides/scan-agent-written-code-with-semgrep-mcp/\">scanning agent-written code with Semgrep</a>.</li><li>A role with no scanners configured returns ALLOW with the reason &quot;default&quot;. If you configure only <code>Role.USER</code>, tool output passes unchecked.</li></ul>\n<p>Gotchas with <code>ScannerType.PROMPT_GUARD</code> in 1.0.3, the latest release on PyPI (May 2025):</p>\n<ul><li><strong>Imports fail with current huggingface_hub.</strong> On a fresh install today, the scanner raised <code>ImportError: cannot import name &#39;HfFolder&#39; from &#39;huggingface_hub&#39;</code> with huggingface_hub 2.4.0. The repository&#39;s main branch has the fix, but it isn&#39;t released. Pin <code>&quot;huggingface_hub&lt;1.0&quot;</code> (we used 0.36.2, with transformers 4.57.6) or use the screener above.</li><li><strong>It always uses the 86M model</strong> (<code>meta-llama/Llama-Prompt-Guard-2-86M</code>), and with no saved token it calls Hugging Face&#39;s interactive <code>login()</code>. In a non-interactive process that ended in <code>EOFError</code>.</li><li><strong>It only scores the first 512 tokens.</strong> The text is tokenized with <code>truncation=True, max_length=512</code>. An injection further down a long page is never seen. Split long output yourself, as in the screener, before you hand it to the scanner.</li><li><strong>Its BLOCK reason includes the full text</strong> (<code>Full text: &quot;...&quot;</code>). Don&#39;t send <code>result.reason</code> to shared logs.</li></ul>\n<p><strong>AlignmentCheck</strong> is different. Instead of looking at the tool output, it asks an LLM whether the agent&#39;s latest action still serves the user&#39;s original request. That can catch injections a classifier misses. In 1.0.3 the firewall builds it with fixed defaults: <code>meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8</code> at <code>https://api.together.xyz/v1</code>, with the key in <code>TOGETHER_API_KEY</code>. It sends the whole trace, user request included, to that endpoint. Check that this is acceptable for your data before you turn it on. We did not run AlignmentCheck.</p>\n<h2>Put it together</h2>\n<ol><li>Mark which tools return outside content: web fetch, search, email, files, other agents and MCP servers you don&#39;t run yourself.</li><li>Screen each result with the screener, plus LlamaFirewall&#39;s hidden-ASCII check or your own patterns. Withhold flagged results and keep them for review.</li><li>Limit what a successful injection can do: read-only credentials, allowlisted destinations and a person approving sends, payments and deletes. The <a href=\"/compare/agent-guardrail-frameworks/\">guardrail frameworks comparison</a> covers policy layers that check tool calls.</li><li>For coding agents, <a href=\"/entries/hol-guard/\">HOL Guard</a> works through the agent&#39;s own hooks and MCP proxies. Its README notes that coverage &quot;depends on the events each agent exposes&quot;, so check its support matrix for your harness.</li><li>Measure. Run <a href=\"/guides/test-an-agent-for-prompt-injection/\">AgentDojo</a> with and without the screener, and add attacks from the <a href=\"/compare/llm-red-teaming-tools/\">red-teaming tools</a>. A guard you haven&#39;t measured is a guess.</li></ol>\n<h2>How we tested</h2>\n<p>Run on 2026-11-03 on CPU (Python 3.13.5, torch 2.15.0+cpu, llamafirewall 1.0.3, codeshield 1.0.1).</p>\n<ul><li>The Prompt Guard 2 repositories are gated (<code>&quot;gated&quot;: &quot;manual&quot;</code> on the Hugging Face API), and an unauthenticated download of <code>config.json</code> returned 401. We had no approved account, so <strong>we did not measure detection quality</strong>. The figures above are Meta&#39;s.</li><li>The screener was run with transformers 4.57.6 and 5.22.1, against a stand-in model: the ungated <code>microsoft/deberta-v3-xsmall</code> checkpoint with an untrained two-label head. The tests checked these code paths:</li><li>real tokenization and forward passes</li><li>every token of a 2,000-token input covered by five windows of at most 512 tokens</li><li>an injection placed after token 2,000 caught by a stub head, which first-512-token truncation missed</li><li>tag characters flagged</li><li>oversized input failing closed</li><li>an uncached gated model id raising <code>OSError</code> at once, with no download or prompt</li><li>the wrapper withholding a flagged result without echoing it</li><li>LlamaFirewall&#39;s HIDDEN_ASCII, REGEX and CODE_SHIELD scanners were run as shown. PROMPT_GUARD was run only far enough to see the import error and the login prompt.</li></ul>"
}
