# Block secrets in a Fastify app

URL: https://tailrace.dev/docs/guides/block-secrets-in-fastify

> Register the OpenAI-compatible Fastify plugin so chat requests and SSE responses respect Tailrace policy before they reach the model.



Use `@tailrace/fastify` to enforce data policy on OpenAI-compatible chat routes - in-process, with no proxy.

## Prerequisites [#prerequisites]

* [Quickstart](/docs/get-started/quickstart) or equivalent [`createTailrace`](/docs/get-started/quickstart) setup
* Fastify `>=4`

## Step 1: Install [#step-1-install]

```bash
pnpm add @tailrace/core @tailrace/fastify fastify
```

## Step 2: Register the plugin [#step-2-register-the-plugin]

```ts
import { createTailrace } from "@tailrace/core";
import { tailraceFastify } from "@tailrace/fastify";
import Fastify from "fastify";

const app = Fastify();
const tailrace = createTailrace();

await app.register(
  tailraceFastify(tailrace, {
    agent: (req) => String(req.headers["x-agent-id"] ?? "default"),
    workflowId: (req) => String(req.headers["x-workflow-id"] ?? "default"),
  }),
);

// Your upstream proxy / chat handler routes...
await app.listen({ port: 3000 });
```

The plugin uses `preHandler` for the request check and `onSend` / stream wrapping for JSON and SSE
responses. Ensure chat routes receive a parsed JSON body (Fastify's default JSON parser is enough).

## How it works [#how-it-works]

| Location                | Tailrace boundary                                   | Direction            |
| ----------------------- | --------------------------------------------------- | -------------------- |
| Chat request messages   | `{ kind: "model", provider }` (`model` field as-is) | Outbound to upstream |
| JSON chat completion    | same model boundary                                 | Inbound              |
| SSE `text/event-stream` | same; carry-buffer across chunks                    | Inbound              |

When policy resolves to `block`, the plugin returns **422** with
`{ error: { type: "policy_violation", entity, rule } }` - never the raw value. That maps from
[`PolicyViolationError`](/docs/reference/errors/POLICY_VIOLATION).

For SSE, Tailrace cancels the upstream stream, emits one error `data:` event, and closes
(abort-only in v0.1).

Only `mode: "openai-compatible"` is supported. Shared helpers live in [`@tailrace/http`](/docs/reference/http).

## Common variations [#common-variations]

### Scope agent and workflow ID [#scope-agent-and-workflow-id]

Pass `agent` and `workflowId` as functions of `FastifyRequest` (or a static string for
`workflowId`). Defaults are `"default"` when omitted.

By default, `workflowId` scopes tokens per request. For multi-turn agents that need tokens to
resolve consistently across turns, see [Tokenization and the vault](/docs/concepts/tokenization-and-the-vault).

### NestJS + Fastify [#nestjs--fastify]

If you use Nest's Fastify adapter, register `@tailrace/fastify` on the underlying Fastify instance
rather than `@tailrace/nestjs` Express-shaped middleware. See
[Block secrets in a NestJS app](/docs/guides/block-secrets-in-nestjs).

### Verify with a synthetic key [#verify-with-a-synthetic-key]

Send a chat completion body whose messages include `sk_test_…FAKE`. Expect **422**. An
`example.com` email should tokenize under the default policy.

## See also [#see-also]

* [Fastify integration](/docs/integrations/fastify)
* [Fastify reference](/docs/reference/fastify)
* [Hono](/docs/integrations/hono) - same openai-compat contract on a different host
* [Boundaries](/docs/concepts/boundaries)
* Spec: [`docs/integrations.md`](https://github.com/TailraceHQ/tailrace/blob/main/docs/integrations.md) §11
