Skip to main content
If your application uses an LLM to drive tool or function calls, scan the arguments before executing them. The tool‑call endpoint is regex‑only (no model inference), so it’s sub‑millisecond on the warm path and safe to put in front of every dispatch.

POST /api/runtime-security/scan/tool-call

{
  "tool_name": "http_get",
  "arguments": {"url": "http://169.254.169.254/latest/meta-data/iam"},
  "source_app": "agent-runtime",
  "metadata": {}
}
The response is a verdict (allow / redact / block) with a list of triggering reasons.
{
  "uuid": "7e…",
  "verdict": "block",
  "reasons": [
    {
      "code": "ssrf.imds",
      "severity": "block",
      "detail": "URL targets cloud instance metadata",
      "match": "169.254.169.254"
    }
  ],
  "redacted_arguments": {"url": "http://169.254.169.254/latest/meta-data/iam"},
  "pii": {"count": 0, "categories": [], "findings": []},
  "arg_bytes": 67,
  "blocked_reason": "ssrf.imds:URL targets cloud instance metadata"
}
Rate limit: 20 requests / second / workspace.

What it catches

CodeWhat triggers it
tool.deniedTool name on the workspace or App denylist.
tool.not_allowedAllowlist configured and the tool is not on it.
tool.args_too_largeArguments exceed max_arg_bytes.
shell.dangerousrm -rf /, fork bombs, curl | sh, base64‑decoded execs, reverse shells.
sql.dangerousDestructive DML (DROP, unscoped DELETE / UPDATE), stacked statements, UNION SELECT FROM pg_*.
ssrf.imdsURL targets cloud instance metadata (169.254.169.254, metadata.google.internal).
ssrf.private_networkURL targets RFC1918 / loopback. Configurable per tenant.
ssrf.schemeDisallowed schemes (file://, gopher://, ldap://, dict://).
path.traversalRepeated ../ segments.
path.sensitive/etc/passwd, ~/.ssh/, ~/.aws/credentials, ~/.kube/config, etc.
Plus full PII detection on string leaves of the arguments, secrets in tool calls get the same treatment as secrets in prompts.

Tool policy (App‑level)

Each App can define an allowlist, a denylist, or both:
{
  "tool_policy": {
    "allowlist": [],
    "denylist": ["delete_user", "drop_table"]
  }
}
  • Allowlist non‑empty + tool not presenttool.not_allowed.
  • Denylist non‑empty + tool presenttool.denied.
  • Both empty → the generic catches (shell.*, sql.*, ssrf.*, path.*) still apply.
The workspace‑level config can layer a base denylist on top.

Wrapping it around your agent

The minimal integration is one HTTP call before every tool dispatch.
import httpx

ANTIDOTE = httpx.Client(
    base_url="https://api.your-antidote.com",
    headers={
        "X-API-Key": "ak_live_…",
        "X-Antidote-App-Id": "app_…",
    },
    timeout=2.0,
)

def safe_dispatch(tool_name: str, arguments: dict):
    r = ANTIDOTE.post(
        "/api/runtime-security/scan/tool-call",
        json={"tool_name": tool_name, "arguments": arguments},
    )
    r.raise_for_status()
    v = r.json()
    if v["verdict"] == "block":
        raise PermissionError(v["blocked_reason"])
    # On `redact`, use redacted_arguments so the tool runs with
    # PII spans replaced.
    return v["redacted_arguments"] if v["verdict"] == "redact" else arguments

SSRF and private networks

The default policy blocks instance‑metadata endpoints and RFC1918 / loopback addresses. For on‑prem agents that legitimately call private services, set agentic.allow_private_network=true on the workspace config (see Configuration). The IMDS guard stays on regardless.
Disabling the private‑network guard removes SSRF protection. Only do it for trusted on‑prem agents inside controlled networks.

What it doesn’t do

  • The tool‑call endpoint inspects arguments, not return values. If your tool returns data that itself needs scanning (a file contents read, a SQL result row), scan it through Scan API /scan/output.
  • It doesn’t run the tool. Your agent runtime still executes; the endpoint just tells it whether to.
  • It doesn’t enforce semantic policies (“this user can only delete their own files”). For those, layer authorization on top.