Repository navigation
Fix email delivery isolation and background failure reporting - #5453
nilsteampassnet merged 7 commits into
Conversation
6fb4a6c to
ce4697c
Compare
There was a problem hiding this comment.
Hi @guerricv, thanks for this PR. Before merging, I'd like the following points addressed.
1. Failed send_email tasks keep their full arguments forever (blocking)
completeTask() anonymizes a send_email task through buildCompletionArguments(), which keeps only the receivers and the login. With this PR, a relay refusal goes through handleTaskFailure() instead. That method writes the status but leaves arguments untouched, so the full body and encryptedUserPassword stay in background_tasks. A body can hold a secret in clear text: for example, the Google Authenticator code sent by email (#2FACode#, app/sources/main.queries.php:1744).
These rows are never cleaned up. The purge of old failed tasks (app/scripts/background_tasks___handler.php:1037-1046) uses an INNER JOIN on background_subtasks, and a send_email task has no subtask. Before this PR, a refused send was marked completed, so it was anonymized and later purged. With a misconfigured relay, every notification body would now pile up with no limit.
Requested:
- in
handleTaskFailure(), apply the same anonymization tosend_email:if ($this->processType === 'send_email') { // Same anonymization as completeTask(): the body and the encrypted credential must not outlive the attempt. $updateData['arguments'] = $this->buildCompletionArguments(); }
- change the purge's
INNER JOINto aLEFT JOIN, so that failed tasks without subtasks are removed too. The bug predates this PR, but it is what makes the retention permanent; - extend
testWorkerPersistsDeliveryFailuresAndLogsOnlyMetadatato assert that thefailedupdate stores the anonymized arguments, without the body or the ciphertext.
2. The test email no longer shows TLS and connection errors
safeDebugLine() drops the lines that tell an administrator why the connection failed. I measured it with PHPMailer 6.12 against 127.0.0.1, at debug level 3:
| Scenario | develop | this PR |
|---|---|---|
| Refused port | Connection failed. Error #2: stream_socket_client(): Unable to connect to 127.0.0.1:1 (Connection refused) [...]SMTP ERROR: Failed to connect to server: Connection refused (111) |
Connection: opening |
| Certificate verification on, invalid certificate | Connection failed. Error #2: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:error:0A000086:SSL routines::certificate verify failed [...] |
Connection: opening |
The second case is exactly what the test email is for since email_smtp_verify_certificate was added: the administrator has to know whether to fix the CA chain or turn verification off. These lines come from the local stream layer and OpenSSL, not from the provider. They contain no credentials, since PHPMailer already hides AUTH below level 4.
Requested:
- keep
Connection failed. Error #N: <message>, without the trailing[file line N]. The OpenSSL message spans several lines, so the pattern needs the/smodifier; - keep
SMTP ERROR: Failed to connect to server: <reason> (<errno>); - keep
SMTP ERROR: <COMMAND> command failed, without the reply text; - keep
Connection: closing due to error; - drop the
failedalternative fromConnection: (opening|opened|closed|failed): PHPMailer 6.12 never emitsConnection: failed; - cover these lines with the exact strings PHPMailer emits (the ones above), not with made-up ones.
3. Decryption failures are reported as delivery failures
EmailTrait throws Email credential could not be decrypted., but handleTaskFailure() replaces every send_email message with Email delivery failed.. An administrator then cannot tell a SECUREFILE problem from a relay refusal. Meanwhile, the documentation tells them to diagnose with the test email, and in that case the test email succeeds. Both messages thrown by the trait are fixed strings, so please keep them: use an allowlist of the trait's own messages in handleTaskFailure(), and fall back to the generic text for anything else.
4. A socket errno is shown as an SMTP code
When the connection fails, PHPMailer stores the socket errno in smtp_code. A three-digit errno passes the [1-5][0-9]{2} check, so a refused connection shows as SMTP Error: Could not connect to SMTP host. [111], which reads like an SMTP reply. Please only append codes when the SMTP error is not a connection failure (Failed to connect to server / Connection failed.), or label the value as an errno. In that case smtp_code_ex holds the local socket message (Connection refused), which is safe to show as well: [errno 111: Connection refused].
5. Documentation
- add
docs/manage/email-troubleshooting.mdtodocs/_sidebar.md, next to Email templates (line 47). Otherwise the page cannot be reached from the docs navigation; - after point 1, rewrite the paragraph "Failed task arguments remain in the database for investigation…": only the recipient and the login will remain;
- update the debug-level paragraph to list the connection and TLS errors kept by point 2.
6. Release note
Administrators will see a different debug output for the test email (a filtered transcript instead of the raw one). Please add a short Release note section to the PR description that describes this change, so I can carry it into the changelog.
Thanks again! I'll take another look as soon as these changes are pushed.
ce4697c to
e10931b
Compare
|
Hi Nils, thanks for the precise checks and repro cases. I've rebased onto
I've also added the requested Release note section to the description, leaving the existing review context intact. Final validation on In an isolated Linux copy, PHP 8.5.4 also passed 3,554 tests / 83,974 assertions (14 deprecations and five skips reported). The MariaDB cleanup test passed locally too. An additional loopback test with a real untrusted certificate confirmed that verification rejects it and the OpenSSL cause remains visible without source paths or false SMTP codes. The simulator was stopped and its temporary certificate/key removed; the installed application was not changed. Scrutinizer is green and reports one non-blocking finding. I also checked the file-write taint trace at |
Description
Reusing the email service for a backlog or an administrator loop could carry recipients from one message into the next. Background notifications also ignored SMTP failures and could mark a rejected email as completed.
This fixes those delivery problems and prevents SMTP diagnostics and task logs from exposing credentials or message contents. The existing SMTP settings and return contract are preserved; no reconfiguration or database migration is required.
The changes are split into three commits:
50200b002: isolate recipient lists between deliveries and close the connection after a failure.98ef52a8b: report failed background deliveries, skip empty credential decryption, and refuse delivery when a credential cannot be decrypted.ce4697c55: retain only safe SMTP stages, library reasons and validated numeric codes; replace task argument dumps with task metadata; add troubleshooting documentation.Related issue
No existing issue. This is a standalone prerequisite for the email multi-transport work in #5454, kept in draft while this prerequisite is integrated and acceptance checks are completed. This corrective PR adds no new transport or automatic retry.
Type of change
How has this been tested?
Rebase follow-up (8 November 2026): rebased the three separate commits onto current
developat2e3db0edde1b4bfb1a93c84f5944e964c278b548(TeamPass 3.2.4.2), without conflicts or patch changes. Current head:ce4697c5509205cfe9cf83874bfe5380397bb4ad. Local email/background-task tests passed (129 tests, 1,289 assertions; five environment skips). Fresh Quality CI passed on this rebased head: PHP 8.2/8.3 each passed 3,538 tests and 83,901 assertions; all 311 JavaScript tests, PHPStan level 4, MariaDB and repository guards passed. CodeQL and external Scrutinizer also passed on this rebased head; all 11 checks are green. The results below are historical checks of the earlier head. No manual role or real-provider acceptance is claimed.6fb4a6c3c: 3,504 tests, 83,394 assertions, no errors or failures.2783652a2was tested in the same Linux environment: 3,494 tests, 83,304 assertions. Both runs reported 14 deprecations and 5 skipped tests.git diff --checkpassed.These checks use no installation credentials or real SMTP relay. No manual UI or user-role validation is claimed; access-control code is unchanged. The complete Windows run encountered CRLF/OpenSSL/POSIX environment differences; the complete Linux comparison used LF archives and the required test extensions. GitHub Actions checks passed on
6fb4a6c3c, including PHP 8.2/8.3, PHPStan level 4, JavaScript, MariaDB integration tests, CodeQL and the repository guards. The external Scrutinizer check also passed on this commit.Checklist
var_dump()orconsole.log()Impact on install / upgrade
Existing SMTP authentication, encryption and certificate verification settings are retained.
Release note
The test-email dialog now shows a filtered SMTP transcript: connection stages, numeric replies, failed command names and local connection/TLS/OpenSSL errors remain visible, while authentication data, message contents and raw server replies are omitted. Socket error numbers are no longer displayed as SMTP reply codes.
Screenshots
Not applicable: no interface layout changes.