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.

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

Create Custom Alert Rules From the Command Line

SecureNow 8.1 adds `securenow alerts rules create` — define your own detection rules from SQL, scope them to your apps, and ship them without leaving the terminal. Here's how, with a real magic-link brute-force example.

Jun 11
Secure a Next.js App with SecureNow Using This AI Onboarding Prompt

A copy-paste prompt that lets an AI coding agent install SecureNow, wire Next.js instrumentation, verify traces and logs, deploy to AWS, simulate attacks, and prove firewall blocking with human approval gates.

May 18
nextjs securenow ai onboarding prompt
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