Capturing Request Bodies for Forensics in Nuxt Without Leaking Secrets

Nuxt server middleware + h3 utilities give you a clean hook for body capture. Here's the redaction-aware pattern that works on Nuxt 3 and self-hosted Nitro.

Lhoussine
May 9, 2026·4 min read

Capturing Request Bodies for Forensics in Nuxt Without Leaking Secrets

Nuxt 3 server routes (in server/api/) get the body via readBody(event). Add a server middleware that runs before route handlers to capture it once.

The redaction utility

// server/utils/redact.ts
const SENSITIVE = new Set([
  'password', 'pass', 'pwd', 'token', 'access_token', 'refresh_token',
  'api_key', 'apikey', 'secret', 'authorization', 'cookie', 'session',
  'creditcard', 'card_number', 'cvv', 'cvc', 'ssn',
]);

export function redact(obj: any, depth = 0): any {
  if (depth > 10) return '[max-depth]';
  if (obj == null || typeof obj !== 'object') return obj;
  if (Array.isArray(obj)) return obj.map(item => redact(item, depth + 1));

  const result: any = {};
  for (const [key, value] of Object.entries(obj)) {
    if (SENSITIVE.has(key.toLowerCase())) result[key] = '[REDACTED]';
    else if (typeof value === 'object') result[key] = redact(value, depth + 1);
    else result[key] = value;
  }
  return result;
}

The middleware

// server/middleware/forensics.ts
import { trace } from '@opentelemetry/api';
import { redact } from '../utils/redact';

export default defineEventHandler(async (event) => {
  const path = getRequestURL(event).pathname;
  if (!path.startsWith('/api/')) return;

  // Read body — must use a non-destructive method since route handler will also call readBody
  const body = await readBody(event).catch(() => null);
  const headers = getHeaders(event);

  const span = trace.getActiveSpan();
  if (span) {
    if (body) {
      const safe = JSON.stringify(redact(body)).slice(0, 8192);
      span.setAttribute('http.request.body', safe);
    }
    span.setAttribute('http.request.headers', JSON.stringify(redactHeaders(headers)));
  }
});

function redactHeaders(headers: Record<string, string | undefined>) {
  const result: Record<string, string> = {};
  for (const [key, value] of Object.entries(headers)) {
    if (!value) continue;
    const lower = key.toLowerCase();
    result[key] = (lower === 'authorization' || lower === 'cookie' || lower.startsWith('x-api-key'))
      ? '[REDACTED]'
      : String(value);
  }
  return result;
}

Caveat: body consumption

readBody() consumes the request stream. If the middleware reads it, the route handler can't read it again. Two workarounds:

  1. Tee the body: read it in middleware, attach to event context, route handler reads from context.
event.context.cachedBody = await readBody(event);

In your route handler:

const body = event.context.cachedBody ?? await readBody(event);
  1. Use the SecureNow preload, which handles this transparently.

Response capture

// server/middleware/forensics-response.ts
export default defineEventHandler((event) => {
  event.node.res.on('finish', () => {
    // Capture response metadata only; body is harder to intercept
    const span = trace.getActiveSpan();
    span?.setAttribute('http.response.status', event.node.res.statusCode);
  });
});

For full response body capture, use the SecureNow auto-instrumentation — manual response interception in Nitro is fiddly.

The automatic option

node -r securenow/register .output/server/index.mjs

Auto-captures bodies, headers, and response data with redaction across all Nuxt server routes. No middleware needed.

Related

Frequently Asked Questions

Does this work in Nuxt server routes (server/api)?

Yes. Capture happens in middleware that runs before the route handler, or inside the route handler using `readBody()`.

What about for Vercel-deployed Nuxt?

Same code works. The redaction logic doesn't depend on the runtime.

Is there an automatic option?

Yes — preload `securenow/register` and bodies are auto-captured with redaction across all Nuxt server routes.

Recommended reading

Adding Backend Tracing to a Sentry Stack with OpenTelemetry

If your team uses Sentry for frontend errors and needs backend distributed tracing without doubling the Sentry bill, here's the OpenTelemetry path that doesn't make you choose.

May 9
How to Block Bot Traffic in Express With No Extra Infra

Five approaches to bot blocking in Express, ranked by effort vs. effectiveness. From a 5-line allowlist to a full IP-reputation firewall — all without Cloudflare, AWS WAF, or any new infrastructure.

May 9
How to Block Bot Traffic in Fastify With No Extra Infra

Fastify hooks (onRequest) and the SecureNow preload both work cleanly. Here's the production setup for IP blocking and user-agent filtering.

May 9