Repository navigation
[Bug]: Login passwords can appear in nextcloud.log: OC\Log::logException merges an unredacted Normalizer copy of the exception alongside the redacted ExceptionSerializer trace #65398
Description
Summary
An exception logged with ['exception' => $e] ends up in nextcloud.log twice: once as the structured, redacted serialization from OC\Log\ExceptionSerializer (sensitive arguments replaced with *** sensitive parameters replaced ***), and once as a string produced by Nextcloud\LogNormalizer\Normalizer, which embeds Throwable::getTraceAsString(). PHP's text-format trace includes function arguments verbatim, and this second copy bypasses ExceptionSerializer's redaction entirely.
In practice this leaks login passwords (and other runtime secrets) into nextcloud.log whenever an exception is logged while a password-bearing frame is on the stack.
Steps to reproduce
Observed in production on 34.0.1 (steps simplified to the cleanest chain I could find; the bug itself is in the logging layer and affects any ['exception' => $e] log from a password-bearing stack):
- Install NC 34 with default apps (
suspicious_loginis shipped and enabled). Do not configure email (SMTP defaults to127.0.0.1:25), or point mail at an unreachable host. - Log in as a user from an IP the suspicious-login classifier considers a new location.
OCA\SuspiciousLogin\Listener\LoginMailListenerthen synchronously tries to send the "New login location detected" mail inside the login request. - The send fails with
Symfony\Component\Mailer\Exception\TransportException, andOC\Mail\Mailer::send()logs it at error level with the full exception (lib/private/Mail/Mailer.php, theSending mail to ... failedbranch). - The resulting
nextcloud.logentry contains the user's plaintext password twice inside theexception.exceptionstring field:
#24 [internal function]: OC\Server->{closure:...}(Object(OC\User\User), 'admin', '<PLAINTEXT PASSWORD>', false)
...
#39 ...: OC\Core\Controller\LoginController->tryLogin(Object(OC\Authentication\Login\Chain), Object(OC\Security\TrustedDomainHelper), 'admin', '<PLAINTEXT PASSWORD>', true, NULL, 'Europe/Berlin', '60')
while the same entry's structured exception.Trace correctly shows *** sensitive parameters replaced *** for exactly those frames.
Any other code path that logs an exception whose stack passes through a frame holding a secret (auth backends, encryption app, ...) is affected the same way — the mail-during-login chain is just the trigger I hit.
Expected behaviour
No sensitive argument values anywhere in the log entry. The redaction performed by OC\Log\ExceptionSerializer should cover all serialized forms of the exception that reach the log.
Actual behaviour
Two copies of the exception are merged into one entry:
exception.Trace— structured, properly redacted (OC\Log\ExceptionSerializer::filterTrace()matches sensitive methods likecompleteLogin/{closure}, collects the argument values, and scrubs them from the whole structured trace).exception.exception— a JSON-ish string fromNextcloud\LogNormalizer\Normalizer::normalizeException(), whosetracefield is$exception->getTraceAsString(), i.e. raw PHP text trace with all argument values, produced with no redaction hook.
Root cause
OC\Log::logException() (lib/private/Log.php, around line 340 on 34.0.1):
$context = array_map($this->normalizer->format(...), $context); // 'exception' => Normalizer string w/ getTraceAsString()
$data = $context;
unset($data['app'], $data['level']);
$data = array_merge($serializer->serializeException($exception), $data); // redacted structured + raw string, both keptOC\Log\ExceptionSerializer::filterTrace() only ever sees the structured trace; the Normalizer output is merged in afterwards and ships unredacted. The same pattern exists in OC\Log::log()/logData() for arbitrary context values.
Secondary defect in the same field: Normalizer::convertToString() runs str_replace(['\\u0000', '\\\\'], ['', '\\'], ...) over the JSON-encoded string, which un-escapes backslashes inside JSON string literals — so exception.exception is invalid JSON ("class":"Symfony\Component\Mailer\...") and breaks any downstream log parser that tries to decode it (json_decode() returns null).
Suggested fix
Either of:
- In
OC\Log::logException()(andlog()/logData()), stop merging the raw Normalizerexceptionstring into the entry — the structuredExceptionSerializeroutput already carries class/message/trace. This also removes the invalid-JSON field. - Or expose the sensitive values collected by
ExceptionSerializer::filterTrace()and use them to post-redact the Normalizer output before merging (those values already include the login password, since the frames that carry it are on the sensitive-methods list). - Longer term: give
Nextcloud\LogNormalizer\Normalizeran injectable argument-redaction hook so text traces can be scrubbed at the source.
Happy to attempt a PR if maintainers have a preference between these.
Impact note
Found during a real incident: a migration left SMTP unconfigured, one login produced a nextcloud.log entry containing that user's plaintext password, and log files routinely end up in backups/support bundles, which broadens exposure beyond the server itself. Reporting publicly here since exploitation requires log access; if triage prefers to handle this as a security report instead, feel free to move it.
Server configuration
- Nextcloud version: 34.0.1 (installed version string 34.0.1.2), official Docker image
nextcloud:34.0.1 - PHP: 8.5.8
- Bundled lib:
3rdparty/nextcloud/lognormalizer(upstream https://github.com/nextcloud/lognormalizer/), as shipped in 34.0.1
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTo triage
Activity
pk7243959-wq commentedon Oct 11, 2026
Hi @dodysw3, thanks for the detailed report!
I can take this up using Option 1 (unsetting the unredacted
$data['exception']inOC\Log::logException()before merging soExceptionSerializerremains the single source of truth, along with regression unit tests).Could this be assigned to me?
fix(log): prevent unredacted exception traces from leaking into logs (f…
fix(log): prevent unredacted exception traces from leaking into logs (f…
fix(log): prevent unredacted exception traces from leaking into logs (f…