Blog · Security
How to Sanitize a HAR File (Chrome's Export Isn't Enough)
In October 2023, Okta disclosed that a threat actor had accessed its customer support case management system. Among the files the attacker reached were HAR files that customers had uploaded for troubleshooting — and some of those HARs contained live session tokens. Okta reported that the attacker used tokens from those files to hijack the legitimate Okta sessions of five customers. The customers had done exactly what the support process asked: reproduce the problem, export the HAR, attach it to the ticket.
That is the risk in one sentence. A HAR file is a complete record of what your browser sent and received, and for an authenticated session that record includes the credentials that make the session work. Upload it somewhere, and whoever can read the file can often be you.
This post covers what actually needs removing, what Chrome’s built-in sanitized export does and does not handle, and the practical ways to sanitize a HAR file properly — plus the cases where you should not share the raw file at all.
What’s inside a HAR that can burn you
A HAR is plain JSON, so every sensitive value has a predictable address. Knowing the addresses is most of the job:
| What leaks | Where it lives in the JSON |
|---|---|
| Session cookies | entries[].request.cookies, Cookie / Set-Cookie headers |
| Bearer tokens and JWTs | entries[].request.headers (Authorization), and often response bodies from the token endpoint |
| API keys in query strings | entries[].request.url and entries[].request.queryString |
| Passwords in login POSTs | entries[].request.postData.text |
| PII returned by your API | entries[].response.content.text |
| CSRF tokens, session IDs | Custom headers, hidden form fields in postData, Set-Cookie |
A single redacted entry shows how densely these cluster:
{
"request": {
"method": "POST",
"url": "https://app.example.com/api/login?client_key=pk_live_REDACTED",
"headers": [
{ "name": "Authorization", "value": "Bearer eyJhbGciOi...REDACTED" },
{ "name": "Cookie", "value": "session=s%3AREDACTED; csrf=REDACTED" }
],
"postData": {
"mimeType": "application/json",
"text": "{\"email\":\"[email protected]\",\"password\":\"REDACTED\"}"
}
},
"response": {
"headers": [
{ "name": "Set-Cookie", "value": "session=s%3AREDACTED; HttpOnly" }
],
"content": {
"mimeType": "application/json",
"text": "{\"user\":{\"email\":\"[email protected]\",\"ssn_last4\":\"REDACTED\"}}"
}
}
} Two things worth noticing. The password is not in a header anywhere — it is in the POST body, which header-focused sanitization never touches. And the PII is in the response, which most people forget the HAR contains at all.
What Chrome’s sanitized export actually removes
Since Chrome 130, DevTools sanitizes HAR exports by default: the exported log no longer contains Cookie, Set-Cookie, or Authorization headers unless you explicitly opt into the “with sensitive data” export.
That is a genuinely good default, and it would have blunted the Okta incident for many of the affected captures. But read the list again: three headers. The sanitized export still includes:
- Tokens and API keys in URLs and query strings —
?api_key=...,?access_token=...survive untouched - Request bodies — login POSTs, GraphQL mutations carrying secrets, anything in
postData - Response bodies — the token endpoint’s JSON response containing the access token Chrome just stripped from the header, customer PII, internal error messages
- Every non-standard auth header —
X-Api-Key,X-Auth-Token, proxy headers, anything your stack invented
So a “sanitized” Chrome HAR of a login flow can still contain the password that was submitted and the token that came back. The header was stripped; the body that produced the header was not.
Firefox and Safari, as far as I can tell at the time of writing, offer no sanitized export mode at all — the HAR includes cookies and auth headers exactly as captured. Treat any HAR from those browsers as fully sensitive.
Sanitizing by hand: a checklist and where it breaks
If you are going to sanitize manually, work from a checklist, not from scrolling:
Authorization,Cookie,Set-Cookieheaders (already gone if Chrome sanitized)- Custom auth headers:
X-Api-Key,X-Auth-Token,X-Csrf-Token, and whatever your app uses - Query strings:
token,key,apikey,code,state,session postData.texton every POST/PUT/PATCH — especially login, signup, and token endpointsresponse.content.textfor token endpoints and any endpoint returning user data- JWTs anywhere — they follow a recognizable
eyJ...pattern and turn up in bodies, URLs, and localStorage-syncing requests
jq is the right tool for finding these, because it understands the structure. To surface every header that looks like a credential:
jq '[.log.entries[].request.headers[]
| select(.name | test("auth|cookie|token|key"; "i"))
| {name, value: (.value[:24] + "...")}]' capture.har To find URLs carrying secrets in the query string:
jq -r '.log.entries[].request.url
| select(test("token=|key=|secret=|password=|code="))' capture.har And a plain grep catches JWTs wherever they hide, including inside serialized bodies:
grep -oE 'eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+' capture.har | sort -u The finding part scales fine. The editing part is where manual sanitization fails in practice. A capture of a real session is routinely 20–40 MB of single-line JSON with base64-encoded bodies. Editors choke on it, find-and-replace misses URL-encoded and base64-encoded copies of the same token, and one missed occurrence undoes the whole exercise. If a token appears in a header, a query string, and a response body, you have three chances to miss it — and secrets in a HAR are frequently duplicated exactly like that.
Manual editing is defensible for a small, filtered capture of a handful of requests. For anything session-sized, use a tool.
Tools that do this properly
Two open-source options are worth knowing.
Cloudflare HAR Sanitizer — a web tool (also usable as a library) that Cloudflare built after the Okta incident. It runs entirely client-side, so the file never leaves your browser, and it scrubs cookies, sensitive headers, query parameters, POST parameters, and response content of selected MIME types. Its limitation is scope: it targets known field names and patterns, and redaction of arbitrary JSON keys inside POST bodies is limited — a secret under an unusual key name can slip through.
HARmor — Frontegg’s CLI, runnable with npx harmor. It sanitizes cookies, auth headers, query parameters, and named JSON body keys, can trim the file, and can encrypt the output for transport. Because it is a CLI it scripts well — you can put it in the support workflow rather than relying on each engineer remembering a web tool.
Both implement the one trick worth understanding even if you never use them: stripping JWT signatures while keeping the claims. A JWT is three base64url segments — header, payload, signature. Delete only the third segment and the token becomes cryptographically inert: no server will accept it. But the claims stay readable, so whoever debugs the HAR can still see the token’s sub, exp, scopes, and tenant — which is usually exactly what the debugging needed. It is the rare redaction that costs almost nothing in signal.
What no tool can promise is completeness against secrets it does not recognize. A bearer token your app returns under the key "t" in a response body defeats pattern-matching. Sanitizers reduce risk substantially; they do not make a HAR safe in the way a file with no secrets is safe.
Sometimes the answer is to not share the file
Step back from the mechanics: why are you sharing the HAR? Usually because someone else needs to read it — a vendor’s support engineer, a teammate. If what they actually need is the answer the HAR contains, an alternative is to analyze it where it sits and share the findings instead.
That can be as simple as running the jq queries above yourself and pasting the relevant three requests (redacted by hand — feasible at that size) into the ticket. Or using a client-side analyzer: TraceMiner parses the HAR in your browser and lets you ask questions about it in plain language, so the workflow becomes “upload nothing, share conclusions.” For debugging-shaped problems — which request failed, what diverged between a working and broken capture — the conclusions are what the other party wanted anyway.
The raw file only needs to move when the other side genuinely has to inspect it themselves. Then sanitize first, every time.
A short policy for support teams
If your product asks customers for HAR files, you inherit Okta’s problem. A minimal policy:
- Tell customers what a HAR contains before they upload. One sentence in the support macro: “This file may include your session cookies and any data visible on the page during capture.”
- Ask for a filtered capture — only the failing action, XHR/fetch filter on — rather than a whole browsing session. Smaller capture, smaller blast radius.
- Point them at a sanitizer and prefer Chrome’s default sanitized export over the “with sensitive data” variant unless the investigation specifically needs auth headers.
- Treat received HARs as credentials: restricted storage, no ticket attachments mirrored to third-party tools you have not vetted, and deletion when the case closes.
- Assume received tokens are live. If a customer uploads an unsanitized HAR, tell them to sign out of that session (or revoke it) — the tokens in the file remain valid until they expire or are revoked.
Frequently asked questions
Are HAR files safe to share? Not by default. A HAR from an authenticated session typically contains cookies, tokens, and request/response bodies that together amount to a working credential set for the account. It is safe to share only after sanitization — and safest when captured with Chrome’s sanitized export and run through a dedicated sanitizer.
Do HAR files contain passwords?
They can. If the capture included a login, the password is in the POST body at entries[].request.postData.text in plain text (TLS protects it in transit, not in the file). Chrome’s sanitized export does not remove request bodies, so a password submitted during capture survives sanitized export.
Does Chrome’s sanitized export remove everything sensitive?
No. It removes Cookie, Set-Cookie, and Authorization headers and nothing else. Tokens in query strings, custom auth headers like X-Api-Key, request bodies, and response bodies are all still present.
How do I remove tokens from a HAR file? For a small capture, find them with jq or a JWT-pattern grep and redact by hand — checking headers, URLs, and both bodies. For a real session capture, use a purpose-built tool such as Cloudflare’s HAR Sanitizer or HARmor, which also neutralize JWTs by stripping their signatures while keeping claims readable.
The uncomfortable summary: the export step everyone treats as routine produces a file that should be handled like a password. Before your next vendor ticket, know what the format contains, sanitize before anything leaves your machine, and when the goal is debugging rather than file transfer, diff or analyze the capture locally and share what you found. And if the HAR is destined for automation instead of a ticket — say, turning captured API calls into agent tools — sanitization matters twice over, because whatever survives in the file ends up in your tooling.
Keep reading
More guides and writeups from the TraceMiner blog.
Debugging With HAR Files: Diff a Working vs Broken Capture
Debugging with HAR files: capture the flow twice, once working and once broken, diff the traces, and the first divergence is the cause. A practical method.
Read blog → AI AgentsHAR to MCP Server: Give Your AI Agent an API, Not a Browser
Turn a HAR file into an MCP server your Claude Code agent calls directly. Skip the headless browser: faster actions, lower cost, and no brittle selectors.
Read blog → EngineeringReverse Engineer API Authentication: JWTs, Refresh & PKCE
How to reverse engineer API authentication from a HAR file: spot session cookies, JWTs, refresh token rotation, and PKCE — and reproduce each flow in code.
Read blog →