Skip to main content

Input & Output Controls

Tool Security

tool-security checks the whole request, including messages and tool definitions, for attack patterns and sensitive data before the model call. It blocks path traversal, rm -rf, the cloud metadata address, secrets such as private keys, and your own patterns. It can also ask an external firewall instead.

It checks the incoming request only, not the tool calls that the model generates later. Use Agent Firewall for those, and enforce permissions in the tool executor.

Configuration​

policy-config.yaml
pack:
  name: tool-security-example-1
  version: "1.0.0"
  enabled: true

policies:
  chain:
    - tool-security

policy:
  tool-security:
    analysis_mode: local
    blocked_patterns:
      - delete_message

Verify a local block​

Add these tests and run verdictan policy test --json:

Add to policy-config.yaml
testing:
  suites:
    - name: tool-security
      cases:
        - name: blocks-pattern
          input: {messages: [{role: user, content: "Describe delete_message"}]}
          expected: {verdict: block, reason_code: tool-security.matched_blocked_pattern:delete_message}
        - name: allows-hello
          input: {messages: [{role: user, content: "Hello"}]}
          expected: {verdict: allow, reason_code: ok}

Both pass. Note that the first message only names the tool and is still blocked: the control matches literal text, not intent.

Fields​

FieldTypeDefaultDescription
analysis_modelocal or externallocallocal runs the built-in checks. external sends the request to firewall_endpoint.
firewall_endpointstringExternal firewall URL. Required with external.
secret_key_ref{ env: string }Variable with the bearer token for the firewall. store is not supported.
fail_closedbooleantrueA firewall error blocks with true and allows with false.
blocked_entity_typesstring[][]Entity types that block in local mode. Empty uses the built-in set.
blocked_patternsstring[][]Substrings that block in local mode. Case does not matter.

How it works​

  1. The gateway serializes the request JSON into one string.
  2. With external and an endpoint, it asks the firewall and uses its verdict. See External mode. The local checks do not run.
  3. In local mode, it looks for each blocked_patterns entry in the lowercased string: tool-security.matched_blocked_pattern:<pattern>.
  4. Then for each built-in attack pattern: tool-security.matched_pattern:<pattern>.
  5. Then it detects entities. A detected type in the blocked set gives tool-security.detected_entity:<type>.
  6. Nothing matched: allow with tool-security.ok.
  7. The details record flagged and reason.

Built-in attack patterns​

  • ../
  • ..\
  • drop table
  • rm -rf
  • 169.254.169.254
  • file://
  • curl http://localhost

Built-in blocked entity types​

Used when blocked_entity_types is empty:

  • account_number
  • aws_access_key
  • cvv
  • health_plan_beneficiary
  • jwt
  • mrn
  • pan
  • private_key
  • ssn

External mode​

The firewall receives the full request JSON and the detected entities. Use a trusted endpoint that is approved for that data. The local rules do not run in this mode.

The gateway sends a POST with a 3-second timeout and a JSON body with two fields: request (the full request) and entities (what the local detector found). If secret_key_ref names a variable with a value, it is sent as a bearer token.

The gateway reads the answer in this order:

FieldBehavior
verdict or actionBlocks for deny, block, blocked, reject, rejected, flagged, or review. Any other value allows.
flaggedUsed only when verdict and action are missing
reasonRecorded as the reason. Otherwise the reason is external_firewall_checked:<endpoint>.
matched_entitiesRecorded in the decision

The reason code is tool-security.<reason> for both allow and block.

Phase and verdicts​

ItemValue
Input phaseThe serialized request JSON
Output phaseNot checked
Verdictsallow, block
Local, no matchtool-security.ok
Local blocktool-security.matched_blocked_pattern:<pattern>, tool-security.matched_pattern:<pattern>, tool-security.detected_entity:<type>
Firewall errortool-security.external_firewall_client_error:<error>, tool-security.external_firewall_request_failed:<error>, tool-security.external_firewall_invalid_response:<error>
Firewall verdicttool-security.<reason> or tool-security.external_firewall_checked:<endpoint>

Examples​

Local mode with the built-in checks​

Inside policy-config.yaml
policy:
  tool-security:
    analysis_mode: local

External firewall with fail-closed errors​

Inside policy-config.yaml
policy:
  tool-security:
    analysis_mode: external
    firewall_endpoint: https://firewall.example.internal/check
    secret_key_ref:
      env: TOOL_FIREWALL_TOKEN
    fail_closed: true

Limits and failure behavior​

  • Local checks are substring matches on the serialized JSON. They do not parse tool arguments. ../ also matches a relative path in a message.
  • Model tool calls are not checked. Use agent-firewall for them.
  • Lint rejects external without firewall_endpoint. A runtime configuration without an endpoint runs the local checks.
  • A firewall error blocks with fail_closed: true and allows with false. Either way, the reason code names the error.
  • The 3-second timeout is fixed. A slow firewall counts as a failed request.
  • Your blocked_entity_types list replaces the built-in set, so include the built-in types you still need.
  • The first match stops the check. The reason code names one pattern or entity type.

Next steps​