DEV Community

Cover image for Your logs are leaking secrets in 24 languages
Tuncay Aliyev
Tuncay Aliyev

Posted on

Your logs are leaking secrets in 24 languages

Your logs are leaking secrets in 24 languages

Every leaked credential has the same origin story. Nobody commits password = "hunter2" to GitHub anymore — scanners catch that. What actually happens is quieter: someone writes

  logger.info({ user }, 'login ok');
Enter fullscreen mode Exit fullscreen mode

and user happens to carry a session token. The code review looked fine. The log aggregator, the error tracker, and three SaaS vendors now have a copy — forever.

## Field names are the wrong abstraction

The standard fix is a redaction path list: tell pino to hide req.headers.authorization, user.password, and so on. It works until:

  • the secret is in a field called note, because a support agent pasted an AWS key into a ticket;
  • your app logs text in Turkish, and the field is called şifrə, not password;
  • the token is inside a stack trace, not a field at all.

A blocklist of names can't see content. So I built flare-redact to read the values themselves.

## Rule 1: if you can't validate it, don't flag it

Content scanning has a reputation problem: false positives. Nobody keeps a tool that redacts random order IDs.

The fix is checksums. A 16-digit number is only a "credit card" if it passes Luhn. An IBAN has a mod-97 check. National IDs are stricter: India's Aadhaar uses the Verhoeff algorithm, France's NIR carries
official documentation examples.

Token formats do the same job for secrets: sk-ant-…, ghp_…, hvs.…, whsec_… are distinctive enough that false positives are effectively zero. Version 1.1.0 ships 75 detectors.

## Rule 2: hostile input is the normal case

A redactor runs on attacker-influenced text by definition — that's the whole point. Two consequences:

  1. ReDoS is a real threat. Every pattern uses bounded quantifiers; CI runs an adversarial benchmark that feeds pathological input and fails on quadratic blowups.
  2. Supply chain counts. The library has zero runtime dependencies. The SHA-256/HMAC primitives are implemented in-repo and verified against FIPS 180-4 and RFC 4231 test vectors. Releases are built by GitHub Actions and provenance-signed on npm.

## Rule 3: sometimes you need the secret back

The newest place secrets leak is LLM prompts. You can't just mask them — the model's reply references the values.

  import { wrapOpenAI } from 'flare-redact/llm';
  const openai = wrapOpenAI(new OpenAI());
  // prompts are redacted before leaving your process,
  // replies come back with the real values restored
Enter fullscreen mode Exit fullscreen mode

Placeholders are opaque HMAC tokens, consistent per value, so "send it to the same email" still works. The mapping lives in a vault in your process — optionally persisted with AES-256-GCM.

## A war story about provenance

Mid-release, we transferred the repo to a GitHub organization. The next publish failed with E422: npm's provenance check refused the package because repository.url still pointed to the old owner, while
the signed build attestation said the new one.

Annoying? Slightly. But it's the system working: provenance means the npm page cryptographically proves which repo and workflow built the tarball. If your supply chain can't survive a rename, it can't
survive an attacker either. Turn it on for your packages: npm publish --provenance.

## Try it

  npm install flare-redact
Enter fullscreen mode Exit fullscreen mode
  import { redact } from 'flare-redact';
  redact('User alice@corp.com paid with 4242 4242 4242 4242');
  // → 'User a***@*** paid with **** **** **** 4242'
Enter fullscreen mode Exit fullscreen mode

It's MIT, TypeScript, Node 20+/browser/edge. If you can construct a false positive, open an issue — the detector suite grows by counter-example: https://github.com/flare-collection/flare-redact

Top comments (0)