DEV Community

HaoPeng Zhang
HaoPeng Zhang

Posted on

The Log That Told Everyone the Password: Redacting Secrets in n8n Execution Logs

The Log That Told Everyone the Password: Redacting Secrets in n8n Execution Logs

I found it the way you always find these things: by accident, while debugging something else. I opened a failed execution to check why a webhook call had returned a 401, scrolled past the error, and there it was in plain text — a full API key, sitting in the request body of a step I had written weeks earlier. Not in a credential. Not encrypted. Just pasted into a header field because it was faster that day.

n8n credentials are encrypted at rest, and that lulls you into thinking secrets are handled. They are — until the moment a secret leaves the credential store and enters the execution log. And execution logs are exactly where secrets go to be seen: they get screenshotted into Slack threads, pasted into support tickets, exported as workflow JSON, and kept around by default far longer than anyone remembers.

Here is the rule that now governs every workflow I build: a secret may exist in exactly one place — the credential store — and every other place it appears is a leak, even if that place is "only" my own dashboard.

How secrets escape

1. The quick paste. An HTTP Request node with Authorization: Bearer sk-live-... typed directly into a header. It works, so it ships. From then on, every execution of that node stores the key in its input data, and every error from that node can echo it back in the error message.

2. The debug log. A Code node with console.log(response) or console.log($json) during development. The response object contains the token that was sent, or a signed URL, or a session cookie. The log line stays in the execution long after the debugging session ends.

3. The error echo. An API returns a 400 and includes the request it rejected — headers and all — in its error body. n8n faithfully records that error. Your secret is now in the error output of a node you never suspected.

4. The export. You export the workflow to share it, or to back it up, or to move it to another instance. Hardcoded secrets travel with the JSON. So do execution histories if you export those. One careless share and the key is in someone else's repo.

5. The long memory. Execution data is kept by default. A key that leaked once is not a one-time exposure; it is retrievable for as long as that execution record lives.

The fixes, in the order I apply them

Put every secret in a credential, no exceptions. If a node needs a token, it gets a credential of the right type, or a generic credential with the header defined inside the credential — never in the node. If an API has no matching credential type, the generic credential still keeps the value out of the node's stored input. The test is simple: if I can read the secret by opening the node, it is in the wrong place.

Never log a whole object. Log the fields you actually need: status code, record count, the ID of the thing you processed. If you must inspect a full payload during development, do it on a test run with a throwaway key, and delete that execution when you are done. Before any workflow goes live, search it for console.log calls that print $json or a full response.

Scrub before you store or send. If a workflow forwards errors to Slack, email, or a logging sheet, pass the error through a shaping step first: keep the message and status code, drop headers, bodies, and query strings. An alert that says "provider X returned 401 on step Y" is all anyone needs to start debugging; the full request dump is not.

Turn down execution retention for sensitive workflows. If a workflow touches payments, inboxes, or customer data, it does not need its full execution history kept indefinitely. Shorten retention, and treat saved executions as sensitive data in their own right — because that is what they are once a leak has happened.

Grep your exports. Before sharing or committing any workflow JSON, search it for sk-, Bearer, api_key, password, and your providers' key prefixes. This takes ten seconds and has caught things for me that a careful re-read did not. Automate it if you can: a pre-commit check that fails on those patterns is worth more than a policy document.

Rotate, don't just fix. When you find a leaked key, fixing the workflow is only half the job. The key that sat in an execution log, a screenshot, or an old export is still out there. Revoke it and issue a new one. A leak you fixed but didn't rotate is a leak you are still carrying.

The test I run before shipping

I keep a dummy secret — a fake key that looks real — and run it through the workflow end to end: trigger it, force an error, export the workflow, and then search every artifact for the dummy value. If it shows up anywhere outside the credential store, the workflow is not ready. This is the same discipline as testing with fake credit card numbers, applied to the thing developers actually leak.

The uncomfortable truth is that most secret leaks in automation are not breaches. Nobody broke in. A developer pasted a key to save five minutes, a log kept it, and the log outlived everyone's memory of the shortcut. Build the workflow so that the shortcut is not available in the first place, and the log has nothing to tell.


I build n8n automation workflows and write about what breaks in production. The templates are on GitHub: https://github.com/zhp910318/n8n-ai-automation-lab — and the packaged versions are on Payhip: https://payhip.com/haopengailab

Top comments (1)

Collapse
 
lin7c profile image
xcl •

The dummy-secret test is the strongest check here, especially when it covers all five escape paths rather than just the credential field. One gap worth probing is the error-echo case: when a 400 response returns the rejected request headers, a scrubber that only filters outbound request objects before sending can be bypassed completely. Does the shaping step for Slack or email alerts also need to sanitize inbound error bodies and query strings for signed URLs, or is redaction applied at a single point before anything is stored? The retention point matters too — shortening retention limits the exposure window, but a screenshot taken during debugging still preserves the secret outside that window, which makes rotation the only reliable closing step.