Skip to content

Input security

Both protections run on the request before it is cached or sent to a provider.

PII redaction

@callm(block_pii=True)
def support_reply(ticket_text): ...

Detection favours precision: dates, timestamps, version numbers, ISBNs and ordinary number lists are left alone. Phone numbers in national formats other than North American ones (for example 030 12345678) are only detected when written with a + country code.

Detected values are replaced with numbered placeholders. The same value always gets the same placeholder within a request, so the model can still reason about it:

"Refund jane@acme.com, card 4111 1111 1111 1111. Confirm to jane@acme.com."
→ "Refund [EMAIL_1], card [CREDIT_CARD_1]. Confirm to [EMAIL_1]."
Entity Detection
email RFC-style address pattern
phone International numbers written with + (8–15 digits) and North American (415) 555-0100 / 415-555-0100 / 415.555.0100 formats
ssn US SSN format, excluding invalid ranges
credit_card 13–19 digits, Luhn-validated, with a real issuer prefix and card-style grouping
ip_address IPv4 with octet validation (not after version, v, build...)
iban IBAN format, mod-97 checksum-validated
person spaCy PERSON entities (optional)
from callm import PIIConfig

PIIConfig(
    entities=("email", "phone", "ssn", "credit_card", "ip_address", "iban"),
    action="mask",                 # or "block": raise PIIDetectedError, send nothing
    ner=False,                     # True: also detect names (pip install "callm-toolkit[security]"
    ner_model="en_core_web_sm",    #        and python -m spacy download en_core_web_sm)
    roles=("system", "user", "assistant", "tool"),
)

Redaction applies to every text segment of the selected roles — plain strings, OpenAI and Anthropic content blocks, Gemini parts and system instructions — and to text inside structured fields: tool call arguments, Anthropic tool_use inputs and tool_result content, text documents, and Gemini function calls and responses. Binary payloads (images, files) and thinking blocks are never modified. Telemetry records how many values of each entity were masked.

Standalone helpers:

from callm import redact_pii
from callm.security import PIIRedactor

redact_pii("call +1 415 555 0100")         # 'call [PHONE_1]'
PIIRedactor(["email"]).find("a@b.io")      # [PIIFinding(entity='email', start=0, end=6)]

Prompt injection detection

@callm(detect_injection=True)                                  # flag
@callm(detect_injection=InjectionConfig(action="block"))       # refuse

The default detector is a fast, dependency-free scorer with weighted signals for:

  • instruction overrides ("ignore all previous instructions", including German, Spanish and French variants)
  • system-prompt extraction ("repeat your system prompt verbatim")
  • role hijacking and jailbreak personas ("you are now DAN", "developer mode enabled")
  • requests to bypass safety filters or not follow rules
  • fake chat-template tokens and role headers (<|im_start|>system, [INST], ### system:)
  • data exfiltration instructions and markdown image beacons
  • hidden unicode (zero-width and bidi control characters) and large encoded payloads

Signals combine as independent evidence: score = 1 − Π(1 − weight). A score at or above threshold (0.5) is flagged: callm logs a warning and records injection_flagged and injection_score in telemetry — or raises PromptInjectionError with action="block".

By default only user and tool messages are scanned: tool results are the most common path for indirect injection (a fetched web page telling the model what to do), while system prompts are written by you.

Adding an ML classifier

InjectionConfig(
    classifier="protectai/deberta-v3-base-prompt-injection-v2",  # any HF text-classification model
    classifier_label="INJECTION",
    action="block",
)

The classifier runs locally with transformers (install it separately); the final score is the higher of the heuristic and classifier scores.

Defence in depth

No detector catches every attack. Keep tools least-privileged, require confirmation for irreversible actions, and treat model output as untrusted input.

from callm import detect_injection

detect_injection("Please ignore previous instructions").score   # 0.85