~/blog

What a HAR file leaks — and how to scrub it before you share it

published

#security#privacy#http

A stylized HTTP request waterfall of horizontal bars on a dark background, several bars covered by grey redaction blocks while others glow neon green and pink
Generated illustration

TL;DR

A HAR file is a verbatim recording of your network traffic, not a screenshot of it. Every Authorization header, Cookie, Set-Cookie, CSRF token, and request body that crossed the wire while you were recording is sitting in plaintext JSON inside it. Sending an unredacted HAR to a vendor’s support desk hands them a working session. Strip the sensitive fields first.

The problem

Support tells you: “Open DevTools, go to the Network tab, reproduce the bug, then right-click → Save all as HAR and send it over.” You do. The bug gets fixed.

What you also did was email a file containing your live session cookie, your Authorization: Bearer … token, and the JSON body of every form you submitted — including the one with your password in it. A HAR is a .har file, but it is just JSON with a documented schema (HAR 1.2). Anyone who opens it sees exactly what the browser sent and received.

What is actually inside

The structure is log.entries[], one entry per request. Each entry has a request and a response, and the secrets live in predictable places:

FieldWhat it holdsWhy it is dangerous
request.headers[]Authorization, Cookie, X-Api-Key, Proxy-AuthorizationBearer tokens and session cookies are valid until they expire — often hours or days
request.cookies[]Parsed cookie name/value pairsSame session, separately parsed for convenience
request.queryString[]URL query paramsAPI keys passed in the URL (?api_key=…) land here
request.postData.textRaw request bodyLogin forms, JSON payloads, PII, passwords, card numbers
response.headers[]Set-CookieFreshly issued session cookies
response.content.textRaw response bodyAPI responses with tokens, email addresses, internal IDs

A single login flow captured in a HAR typically contains the password (in request.postData.text), the issued session cookie (in response.headers Set-Cookie), and that same cookie replayed on every subsequent request.

Why it happens

DevTools records at the HTTP layer, below TLS. TLS protects the bytes in transit; once your browser has decrypted a response to render it, the plaintext is available to the Network panel, and the HAR export serializes that plaintext. There is no “secrets” layer the browser knows to omit — Authorization is just another header to it. The format was designed for performance analysis (waterfall timings live in entry.timings), and the full request/response capture is a side effect that nobody scrubs by default.

Chrome added a “redact sensitive data” checkbox to its HAR export in 2024, but it only masks a known list of auth-style fields and does not touch postData or response bodies. Treat it as a partial measure, not a guarantee.

What to do

Inspect first, then redact. The safest inspection is local: the HAR viewer on this site parses the file entirely in your browser — nothing is uploaded — so you can see which entries carry credentials before you decide what to share.

To redact programmatically, the HAR is just JSON. This Node script blanks the common offenders and drops all bodies:

import { readFileSync, writeFileSync } from 'node:fs';

const SENSITIVE = new Set([
  'authorization',
  'cookie',
  'set-cookie',
  'x-api-key',
  'proxy-authorization',
]);

const redactHeaders = (headers = []) =>
  headers.map((h) =>
    SENSITIVE.has(h.name.toLowerCase()) ? { ...h, value: '[REDACTED]' } : h,
  );

const har = JSON.parse(readFileSync(process.argv[2], 'utf8'));

for (const { request, response } of har.log.entries) {
  request.headers = redactHeaders(request.headers);
  response.headers = redactHeaders(response.headers);
  request.cookies = [];
  response.cookies = [];
  if (request.postData) request.postData.text = '[REDACTED]';
  if (response.content) delete response.content.text;
}

writeFileSync(process.argv[3] ?? 'redacted.har', JSON.stringify(har, null, 2));

Run it: node redact-har.mjs raw.har redacted.har. Adjust the SENSITIVE set and the body handling to your case — if the bug is in a response body, you may need to keep that body but strip a token out of it by hand.

Beyond the script:

Caveats

References