Fail-closed authorization
A side-effecting tool won't even define without a gate. Every call checks the untrusted arguments against the trusted caller context before the handler runs.
Per-call authorization, idempotency, and a tamper-evident audit trail for agent tools, in-process.
Flue's own guidance: "a tool's parameters are model-selected inputs, not an authorization boundary." flue-guard is that boundary. The model controls the arguments; your application controls the context. Every call is decided by comparing the two, then written to a hash-chained receipt.
import * as v from "valibot";
import { govern, caller } from "flue-guard";
declare const accounts: {
ownedBy(accountId: string, actorId: string): Promise<boolean>;
sendResetLink(accountId: string): Promise<void>;
};
const gov = govern({ audit: "audit.jsonl" }); // hash-chained JSONL receipt
export const resetPassword = gov.tool({
name: "reset_password",
description: "Send a password reset link.",
parameters: v.object({ accountId: v.string() }),
sideEffect: true,
// The check that was missing in the Meta incident:
authorize: caller(
(a: { accountId: string }, ctx) => accounts.ownedBy(a.accountId, ctx.actor.id),
),
idempotency: { key: (a) => `reset:${a.accountId}` }, // a retry won't send twice
execute: async (a) => {
await accounts.sendResetLink(a.accountId);
return "Sent.";
},
});For dispatched Flue 2 agents, call gov.withContext(trustedContext) inside the agent function, then mount bound.tool(...) with useTool. Derive the caller from authenticated signal attributes returned by useDelivery(). An ambient gov.run(...) around dispatch does not reach the detached agent execution. See the dispatched-agent example.
Start with the tutorial: from npm i to a denied call and a verified audit line in under five minutes.