security: redact mnemonic-shaped sequences from SDK default logger output#20
Merged
Sentinel-Bluebuilder merged 1 commit intomasterfrom Apr 27, 2026
Merged
Conversation
…tput
The SDK's default loggers (`defaultLog` in connection/connect.js and
node-connect.js, plus inline `opts.log || console.log` fallbacks in
recoverSession and tryFastReconnect) wrote directly to console.log. The SDK
itself never logs the mnemonic today, but a future template-string bug
(e.g. `log(`opts: ${JSON.stringify(opts)}`)` or `log(`derive failed for
${opts.mnemonic.slice(0,4)}...`)`) would leak the BIP-39 phrase to stdout —
and from there to terminal scrollback, CI logs, log aggregators, and shell
history.
Add connection/logger.js exporting `withMnemonicRedaction(logFn)`, which:
- runs the underlying logger after replacing any 12/15/18/21/24-word
lowercase BIP-39-shaped sequence with `[REDACTED MNEMONIC]`,
- short-circuits on strings shorter than 60 characters (cheap fast path),
- only inspects string args (does not recurse into objects — avoids
triggering side-effecting custom getters),
- pass-through for non-function input so callers can still disable
logging by passing `null`.
Wire it into:
- connection/connect.js defaultLog assignment + 1 stale fallback
- node-connect.js defaultLog assignment + 2 stale fallbacks
- connection/disconnect.js new module-local defaultLog (recoverSession)
- connection/resilience.js new module-local defaultLog (tryFastReconnect)
User-supplied `opts.log` is still honored verbatim — we do not wrap external
loggers, only our own defaults. This is defense-in-depth against future bugs
in OUR code, not a constraint on the consumer's logging stack.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Severity: MEDIUM — Defense-in-depth against accidental mnemonic leaks through the SDK's default logger.
The SDK's default
defaultLogisconsole.logand several inline fallbacks (opts.log || console.log) bypass it entirely. The SDK does not currently log the mnemonic. This PR is a safety net so a future bug doesn't.Concrete leak scenarios this would catch:
log(derive failed: opts=${JSON.stringify(opts)})—opts.mnemonicends up in stdout.log(connecting with phrase ${opts.mnemonic.slice(0,4)}...)for "debugging".Sentry.breadcrumb({ msg: arg })integration capturing the same arg the logger received.Once a 12-word phrase touches stdout, it's in terminal scrollback, CI logs, log-aggregation pipelines (Datadog, Loki, Sentry), and shell history — and the wallet is permanently exposed.
Fix
New file
connection/logger.jsexportswithMnemonicRedaction(logFn), which wraps any logger so 12/15/18/21/24-word lowercase BIP-39-shaped sequences in its arguments are replaced with[REDACTED MNEMONIC]before the underlying logger sees them.Wired into:
connection/connect.js—defaultLog+ 1 staleopts.log || console.logfallbacknode-connect.js—defaultLog+ 2 stale fallbacks (recoverSession,connectAutoretry loop)connection/disconnect.js— new module-localdefaultLogforrecoverSessionconnection/resilience.js— new module-localdefaultLogfortryFastReconnectDesign notes
typeof === 'string'arguments and returns everything else unchanged. We deliberately don't recurse into objects — that would risk triggering side-effecting custom getters and turn a logging utility into a footgun.opts.log = mySpecialLogger, your logger gets the raw values. We only redact our own defaults — this is about catching our future bugs, not constraining your logging stack.withMnemonicRedaction(null) === null, so callers can still disable logging viadefaultLog = null.Test plan
Smoke-tested locally:
node --checkpasses on all 5 modified filesdefaultLog = nullstill works)opts.log'wallet','handshake', etc.) are unaffected