Block secrets in an Express app
Mount OpenAI-compatible middleware so chat requests and SSE responses respect Tailrace policy before they reach the model.
Use @tailrace/express to enforce data policy on OpenAI-compatible chat routes - in-process, with no proxy.
Prerequisites
- Quickstart or equivalent
createTailracesetup - Express
>=4
Step 1: Install
pnpm add @tailrace/core @tailrace/express expressStep 2: Mount the middleware
import { createTailrace } from "@tailrace/core";
import { tailraceExpress } from "@tailrace/express";
import express from "express";
const app = express();
const tailrace = createTailrace();
app.use(
"/v1",
express.json(),
tailraceExpress(tailrace, {
agent: (req) => String(req.headers["x-agent-id"] ?? "default"),
workflowId: (req) => String(req.headers["x-workflow-id"] ?? "default"),
}),
);
// Your upstream proxy / chat handler on /v1/...express.json() (or equivalent) must run before Tailrace so the chat body is available as parsed JSON.
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 middleware returns 422 with
{ error: { type: "policy_violation", entity, rule } } - never the raw value. That maps from
PolicyViolationError.
For SSE, Tailrace cancels the upstream stream, emits one error data: event, and closes
(abort-only; no streamBlockBehavior in v0.1).
Only mode: "openai-compatible" is supported. Shared helpers live in @tailrace/http.
Common variations
Scope agent and workflow ID
Pass agent and workflowId as functions of Request (or a static string for workflowId) so
policy and vault tokens match your multi-tenant layout. 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.
Verify with a synthetic key
Send a chat completion body whose messages include sk_test_…FAKE. Expect 422 and an empty
upstream call. An example.com email in the same body should become a vault token in the
forwarded payload when the default policy tokenizes PII.
Audit decisions
tailraceExpress(tailrace, {
onDecision: (decisions) => {
// hashes + entity classes only - never log raw values
},
});See also
- Express integration
- Express reference
- Hono - same openai-compat contract on a different host
- Boundaries
- Spec:
docs/integrations.md§10