Skip to content

Validate SMTP server certificate on STARTTLS upgrade#65346

Merged
potiuk merged 2 commits intoapache:mainfrom
potiuk:validate-cert-on-smtp-starttls
Apr 16, 2026
Merged

Validate SMTP server certificate on STARTTLS upgrade#65346
potiuk merged 2 commits intoapache:mainfrom
potiuk:validate-cert-on-smtp-starttls

Conversation

@potiuk
Copy link
Copy Markdown
Member

@potiuk potiuk commented Apr 16, 2026

Summary

smtplib.SMTP.starttls() does not validate the server certificate unless an SSL context is passed. airflow.utils.email.send_mime_email and the SMTP provider's SmtpHook (both sync get_conn and async aget_conn) were calling starttls() without a context, so the STARTTLS upgrade skipped certificate validation.

This PR passes the existing SSL-context machinery to starttls() at all three call sites:

  • airflow-core/src/airflow/utils/email.py — factored the SSL-context lookup out of _get_smtp_connection into a helper _get_ssl_context(), driven by the existing email.ssl_context config. The helper is now called by both the SMTP_SSL path and the new STARTTLS path.
  • providers/smtp/src/airflow/providers/smtp/hooks/smtp.py — factored the SSL-context lookup into _build_ssl_context(), driven by the existing ssl_context connection extra. The helper is called for SMTP_SSL (sync), for aiosmtplib.SMTP construction (async), and for the explicit starttls() calls in both get_conn and aget_conn.

Default behaviour change

The default SSL context is now ssl.create_default_context(), which validates against the system's trusted CAs. Environments that intentionally use self-signed or otherwise non-validating SMTP servers can opt out:

  • Core: set email.ssl_context = "none" in airflow.cfg
  • Provider: set ssl_context: "none" in the SMTP connection extras

The opt-out already existed for the SMTP_SSL path; it now applies to the STARTTLS path too.

Test plan

  • airflow-core/tests/unit/utils/test_email.py — 25/25 pass
  • providers/smtp/tests/unit/smtp/hooks/test_smtp.py — 34/34 pass (including sync test_ehlo_called_after_starttls and async test_async_connection updated to verify starttls is invoked with an ssl.SSLContext)
Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.6 (1M context)

Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions

potiuk added 2 commits April 16, 2026 04:45
smtplib.SMTP.starttls() does not validate the server certificate
unless an SSL context is passed. airflow.utils.email.send_mime_email
and the SMTP provider's SmtpHook (both sync get_conn and async
aget_conn) were calling starttls() without a context, so the STARTTLS
upgrade accepted any certificate and the subsequent login() call
could send credentials over a connection terminated by a MITM.

Pass the existing SSL-context machinery (the email.ssl_context
config in core and the ssl_context connection extra in the provider)
to starttls() at all three call sites. The default becomes
ssl.create_default_context(), which validates against the system's
trusted CAs. Users who intentionally use self-signed certificates
can still opt out by setting the value to "none".

Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions
Document the default behaviour change introduced by passing an SSL
context to the STARTTLS upgrade: system-default CA validation now
applies to both airflow.utils.email.send_email (via
email.ssl_context) and the SMTP provider's SmtpHook (via the
ssl_context connection extra). Users who intentionally run against
self-signed SMTP servers can preserve the old behaviour by setting
the value to "none".

Generated-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@potiuk potiuk requested a review from hussein-awala as a code owner April 16, 2026 02:50
@potiuk potiuk added the backport-to-v3-2-test Mark PR with this label to backport to v3-2-test branch label Apr 16, 2026
Comment thread airflow-core/newsfragments/65346.significant.rst
@potiuk potiuk merged commit 06981d4 into apache:main Apr 16, 2026
95 checks passed
@potiuk potiuk deleted the validate-cert-on-smtp-starttls branch April 16, 2026 10:01
@github-actions github-actions Bot added this to the Airflow 3.2.2 milestone Apr 16, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Hi maintainer, this PR was merged without a milestone set.
We've automatically set the milestone to Airflow 3.2.2 based on: backport label targeting v3-2-test
If this milestone is not correct, please update it to the appropriate milestone.

This comment was generated by Milestone Tag Assistant.

github-actions Bot pushed a commit that referenced this pull request Apr 16, 2026
)

* Validate SMTP server certificate on STARTTLS upgrade

smtplib.SMTP.starttls() does not validate the server certificate
unless an SSL context is passed. airflow.utils.email.send_mime_email
and the SMTP provider's SmtpHook (both sync get_conn and async
aget_conn) were calling starttls() without a context, so the STARTTLS
upgrade accepted any certificate and the subsequent login() call
could send credentials over a connection terminated by a MITM.

Pass the existing SSL-context machinery (the email.ssl_context
config in core and the ssl_context connection extra in the provider)
to starttls() at all three call sites. The default becomes
ssl.create_default_context(), which validates against the system's
trusted CAs. Users who intentionally use self-signed certificates
can still opt out by setting the value to "none".

Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions

* Add newsfragment and SMTP provider changelog for STARTTLS cert default

Document the default behaviour change introduced by passing an SSL
context to the STARTTLS upgrade: system-default CA validation now
applies to both airflow.utils.email.send_email (via
email.ssl_context) and the SMTP provider's SmtpHook (via the
ssl_context connection extra). Users who intentionally run against
self-signed SMTP servers can preserve the old behaviour by setting
the value to "none".
(cherry picked from commit 06981d4)

Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Generated-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
v3-2-test PR Link

github-actions Bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 16, 2026
…che#65346)

* Validate SMTP server certificate on STARTTLS upgrade

smtplib.SMTP.starttls() does not validate the server certificate
unless an SSL context is passed. airflow.utils.email.send_mime_email
and the SMTP provider's SmtpHook (both sync get_conn and async
aget_conn) were calling starttls() without a context, so the STARTTLS
upgrade accepted any certificate and the subsequent login() call
could send credentials over a connection terminated by a MITM.

Pass the existing SSL-context machinery (the email.ssl_context
config in core and the ssl_context connection extra in the provider)
to starttls() at all three call sites. The default becomes
ssl.create_default_context(), which validates against the system's
trusted CAs. Users who intentionally use self-signed certificates
can still opt out by setting the value to "none".

Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions

* Add newsfragment and SMTP provider changelog for STARTTLS cert default

Document the default behaviour change introduced by passing an SSL
context to the STARTTLS upgrade: system-default CA validation now
applies to both airflow.utils.email.send_email (via
email.ssl_context) and the SMTP provider's SmtpHook (via the
ssl_context connection extra). Users who intentionally run against
self-signed SMTP servers can preserve the old behaviour by setting
the value to "none".
(cherry picked from commit 06981d4)

Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Generated-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
karenbraganz pushed a commit to karenbraganz/airflow that referenced this pull request Apr 16, 2026
* Validate SMTP server certificate on STARTTLS upgrade

smtplib.SMTP.starttls() does not validate the server certificate
unless an SSL context is passed. airflow.utils.email.send_mime_email
and the SMTP provider's SmtpHook (both sync get_conn and async
aget_conn) were calling starttls() without a context, so the STARTTLS
upgrade accepted any certificate and the subsequent login() call
could send credentials over a connection terminated by a MITM.

Pass the existing SSL-context machinery (the email.ssl_context
config in core and the ssl_context connection extra in the provider)
to starttls() at all three call sites. The default becomes
ssl.create_default_context(), which validates against the system's
trusted CAs. Users who intentionally use self-signed certificates
can still opt out by setting the value to "none".

Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions

* Add newsfragment and SMTP provider changelog for STARTTLS cert default

Document the default behaviour change introduced by passing an SSL
context to the STARTTLS upgrade: system-default CA validation now
applies to both airflow.utils.email.send_email (via
email.ssl_context) and the SMTP provider's SmtpHook (via the
ssl_context connection extra). Users who intentionally run against
self-signed SMTP servers can preserve the old behaviour by setting
the value to "none".

Generated-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 23, 2026
)

* Validate SMTP server certificate on STARTTLS upgrade

smtplib.SMTP.starttls() does not validate the server certificate
unless an SSL context is passed. airflow.utils.email.send_mime_email
and the SMTP provider's SmtpHook (both sync get_conn and async
aget_conn) were calling starttls() without a context, so the STARTTLS
upgrade accepted any certificate and the subsequent login() call
could send credentials over a connection terminated by a MITM.

Pass the existing SSL-context machinery (the email.ssl_context
config in core and the ssl_context connection extra in the provider)
to starttls() at all three call sites. The default becomes
ssl.create_default_context(), which validates against the system's
trusted CAs. Users who intentionally use self-signed certificates
can still opt out by setting the value to "none".

Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions

* Add newsfragment and SMTP provider changelog for STARTTLS cert default

Document the default behaviour change introduced by passing an SSL
context to the STARTTLS upgrade: system-default CA validation now
applies to both airflow.utils.email.send_email (via
email.ssl_context) and the SMTP provider's SmtpHook (via the
ssl_context connection extra). Users who intentionally run against
self-signed SMTP servers can preserve the old behaviour by setting
the value to "none".
(cherry picked from commit 06981d4)

Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Generated-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providers backport-to-v3-2-test Mark PR with this label to backport to v3-2-test branch kind:documentation provider:smtp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants