Skip to content

Agent Tools Dispatch

The Ironflow server’s agent_tools.Dispatcher POSTs HMAC-signed callbacks to SDK-registered tool handlers. SDKs (@ironflow/node/agent, sdk/go/ironflow/agent) mount the handler at /ironflow/agent-tools/dispatch.

Header Value
X-Ironflow-Timestamp unix seconds, must be within 5 minutes past / 1 minute future
X-Ironflow-Signature sha256=<hex> of HMAC-SHA256(secret, "{ts}.{rawBody}")

Body: {"qualified_name": "<agent>.<tool>", "input": {...}}

Status Code When
200 Handler ran. {output} on success, {error: {code: "HANDLER_ERROR", message}} on throw.
400 INVALID_REQUEST Body not JSON, or qualified_name missing.
400 INPUT_SCHEMA_INVALID Input failed Zod / Validate() schema check.
401 TIMESTAMP_SKEW Timestamp outside replay window (5min past / 1min future), or unparseable.
401 SIGNATURE_MISMATCH Missing/malformed sig or timestamp header, HMAC verification failed, OR qualified_name not registered. Missing-header and unknown-tool cases collapse to the same code so an unauthenticated caller cannot probe the registered tool roster (see below).

Why unknown-tool collapses to SIGNATURE_MISMATCH

Section titled “Why unknown-tool collapses to SIGNATURE_MISMATCH”

An unauthenticated caller could probe the dispatch endpoint with arbitrary qualified_name values and distinguish 404 (unknown) from 401 (registered, bad sig) — leaking the registered tool roster to anyone who can reach the callback URL. The SDK collapses both cases to 401 SIGNATURE_MISMATCH "HMAC mismatch" so the response shape is identical.

Operators distinguish legitimate stale-process drift (SDK restarted without re-registering) from attack via the SDK’s structured warn log:

ironflow.agent.dispatch unknown_tool qualified_name="<name>"

Emitted once per unknown-tool dispatch. Aggregating these by source IP / caller key in your log pipeline highlights probe attempts. The line is diagnostic only; the wire response remains SIGNATURE_MISMATCH. The qualified_name is rendered with Go %q / JS JSON.stringify so embedded control chars (\n, \r, etc.) are escaped — log injection via crafted callback payloads is blocked.