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.

HeaderValue
X-Ironflow-Timestampunix seconds, must be within 5 minutes past / 1 minute future
X-Ironflow-Signaturesha256=<hex> of HMAC-SHA256(secret, "{ts}.{rawBody}")

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

StatusCodeWhen
200Handler ran. {output} on success, {error: {code: "HANDLER_ERROR", message}} on throw.
400INVALID_REQUESTBody not JSON, or qualified_name missing.
400INPUT_SCHEMA_INVALIDInput failed Zod / Validate() schema check.
401TIMESTAMP_SKEWTimestamp outside replay window (5min past / 1min future), or unparseable.
401SIGNATURE_MISMATCHMissing/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.