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.
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:
- 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);
- 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
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 11A 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
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