QoreDB LogoQoreDB
Back to blog
SecurityTechnical journal

Automatic secret redaction in QoreDB

A database client constantly handles information that should never be copied anywhere else: connection URIs with cleartext passwords, queries containing secrets as literals, API tokens passed as arguments to a Redis command. Yet these same tools continuously write to a local…

Raphaël – Creator of QoreDBRaphaël – Creator of QoreDB
6 min readUpdated Jul 18, 2026
Automatic secret redaction in QoreDB

A database client constantly handles information that should never be copied anywhere else: connection URIs with cleartext passwords, queries containing secrets as literals, API tokens passed as arguments to a Redis command. Yet these same tools continuously write to a local history, an error panel, an audit file, or an export the user will share with a colleague. The risk of leakage isn't a spectacular breach; it's the quiet accumulation of secrets in files nobody thinks to inspect.

QoreDB applies automatic redaction at every persistence boundary, with a strategy specific to the targeted engine. The rule is simple: the raw query never leaves the execution zone; anything that goes out to disk, to long-term memory, or to an export passes through a redaction pass that replaces sensitive literals with markers.

The Sensitive type that makes a leak fail to compile

On the Rust side, the first line of defense isn't a filter, it's the type system. A Sensitive<T> wrapper encloses any data to be protected, and its implementations of Debug, Display and Serialize all render [REDACTED] or *** instead of the real content. To access the value, the code must explicitly call .expose(). That call becomes a visible marker in the code, easy to audit.

The value of this approach is that it moves the guarantee from runtime to compile time. A developer who logs a struct containing a Sensitive field cannot accidentally write the password to the output: the formatter will return [REDACTED] before the tracing macro even gets control. It's a static guarantee, not an operational promise.

One redaction pipeline per engine, because the syntax differs

A single universal regular expression cannot cover SQL, MongoDB and Redis without breaking legitimate queries or letting secrets slip through. QoreDB therefore chooses three specialized pipelines, selected from the driver identifier at persistence time.

For SQL drivers, three patterns are applied in cascade: URIs of the form postgres://user:pass@host have their credentials replaced with ***, assignments like password=hunter2 become password=***, and all single-quoted literals are uniformly replaced with '[REDACTED]'. This last rule is deliberately broad: an email, a product name, an internal reference are all treated as user data, and therefore redacted. The regex also handles doubled quotes as in standard SQL, so as not to cut a string in the middle of an O'Brien.

For MongoDB, redaction targets JSON fields whose key is sensitive: password, token, secret, api_key, credentials, authorization. Three regex variants cover the three ways a user can write them in the console: a double-quoted key, a single-quoted key, and an unquoted key in the mongo shell syntax style. Other fields are preserved intact so the history stays readable: a filter on age or a projection on a name is not modified.

For Redis, the format is different: no literals, no JSON, but line-oriented commands with positional arguments. Redaction happens line by line, command by command. AUTH has all its arguments masked, whether in single-arg or user+password form. CONFIG SET masks the value only if the key is sensitive (requirepass, masterauth or equivalent). EVAL and EVALSHA have their script collapsed to [REDACTED_SCRIPT]: a Lua script can embed a secret anywhere, so it is removed entirely rather than attempting a syntactic analysis. Finally ACL SETUSER masks the clauses that start with > or < (cleartext passwords) and with # or ! (hashes).

The boundaries where redaction applies

The design rule is to redact only at the moment data crosses a persistence boundary. The in-memory query during its execution must stay intact, because it is used by the security analysis (validation against destructive operations, detection of mutations in production). Redacting too early would break this analysis.

Concretely, redaction is applied in four places. The local query history: every entry stored in the local store goes through redactQuery before being written. Query library exports: a redactQueries toggle produces a portable file free of secrets. Error logs: technical messages are also passed through redactText, because a Postgres error can very well spit the offending query back out with its literals. The Universal Query Interceptor's audit log: the field stored is the redacted query, not the raw version.

This separation has a practical effect: a user can enable profiling, run hundreds of queries containing credentials, then send their audit log to a colleague to help investigate a performance problem. The structures (table, operation, duration) are preserved; the sensitive values are not.

Custom patterns for company-specific secrets

Not all secrets look like a password or a JWT token. A team may have internal identifiers in the format CUSTOMER-XXXX, contract numbers, or promotional codes it considers sensitive. QoreDB exposes a custom-patterns mechanism: a list of user-supplied regular expressions is compiled and applied on top of the per-engine redaction, after the standard patterns.

Invalid regexes are silently ignored at runtime because validation is expected on the configuration side, where the user gets direct feedback. Valid patterns are compiled once, stored behind an RwLock to allow concurrent reads at no cost, and applied to the output of the main pass.

How this shows up in real-world use

When a user runs a query like INSERT INTO users (email, password) VALUES ('a@b.c', 'hunter2'), it executes as-is against Postgres. But the entry the user will find in their history is INSERT INTO users (email, password) VALUES ('[REDACTED]', '[REDACTED]'). The structure is kept, which lets you find the query again later, but no value is exposed.

Likewise, a Redis command AUTH alice hunter2 becomes AUTH *** *** in everything that goes out to disk. A MongoDB URI mongodb+srv://user:pass@cluster.mongodb.net/db becomes mongodb+srv://***@cluster.mongodb.net/db. The hostname and the database path are preserved, which stays useful for debugging, but the username and password are cut out.

Redaction can be disabled globally, which is useful for dev sessions against a test database where no real data flows. It stays enabled by default, and the toggle is local: no opaque mode, no hidden behavior.

Conclusion

QoreDB's automatic redaction combines two levels: a Rust type that makes a leak impossible to compile for internal structures, and three specialized regex pipelines that clean queries at the moment they cross a persistence boundary. The per-engine specialization is a direct consequence of a broader decision: not to paper over the differences between databases. A universal regex would have been simpler to write but would have either broken legitimate queries or let secrets through depending on the format. Preferring three known pipelines, each covering its own domain, gives a more predictable and more auditable result. It's the deliberate trade-off of a desktop tool that must be able to produce a readable export or audit log without ever having to wonder whether a password slipped into it.

Newsletter

Stay updated on new releases

Subscribe to get product releases, new drivers notifications, and technical tutorials.

🎁 Bonus: Get our free SQL Performance Cheat Sheet — 9 pages, PostgreSQL / MySQL / SQLite (PDF)!
Share
Automatic secret redaction in QoreDB - Blog - QoreDB