Securing Perl, six years on
8 min read
perlcpansecuritysupply-chainretrospectiveopen-source
Includes AI-generated writing produced with Claude Fable 5.1 from prompts and source material provided by David E. Weekly. Research, fact-checking, and the appendix table by Claude; prose by David. Model: Claude Fable 5.1. Disclosure level: ai-assisted.
What I asked for, starting in 2018
- The HTTPS ask is older than the 2020 doc: andk/cpanpm#118 (“HTTPS support for repositories”) and the first-pass patch #119 were both filed on 2018-05-24. The issue got zero comments in eight years. andk’s May 2020 review of the patch asked for a graceful fallback when https is unavailable and a config switch; that is the shape
pushy_httpstook when CPAN.pm 2.30 shipped it in December 2021, three and a half years after the ask. I closed 118, 119, and 135 in September 2026. - The June 2020 doc widened that into two goals, one paragraph: CPAN over HTTPS by default; signature verification by default, without installing extra modules or flipping config.
- Where the original lives: decide whether to publish the June 2020 doc alongside this post or link a gist.
- The September 2022 update to the doc already celebrated the HTTPS half. This post is about what happened to the rest.
What happened to transport
- Mirror network retired: the Perl NOC stopped maintaining the mirror list on 2021-02-22; MIRRORED.BY is now a one-entry stub last updated 2021-04-14, and www.cpan.org sits behind Fastly with a Let’s Encrypt cert and HSTS preload. (announcement, SITES.html)
- CPAN.pm 2.30 (December 2021) added
pushy_https, on by default. CPAN 2.38, shipped in perl 5.42 and 5.44, still has it on. (Changes) - FTP deprecated 2022-06-11: “any CPAN clients configured to use them will fail to fetch modules.” As of this post, port 21 on ftp.cpan.org does not answer. (announcement)
- HTTP::Tiny flipped
verify_SSLto on in 0.083 (2023-06-11), marked as a security fix. Perl 5.40 is the first core perl with that default. (Changes) - The 2020 mirror audit (192 mirrors, 99 speaking TLS in May, 106 by late June after emailing operators) mattered for roughly eight months and then the whole mirror layer was retired. Worth a paragraph on effort that helped and then evaporated. My PR adding
dst_httpsto MIRRORED.BY (andk/cpanpm#135) sat open until I closed it in September 2026, against a file that now has one entry. - What is still plaintext in 2026:
http://www.cpan.org/modules/02packages.details.txt.gzreturns 200 with no redirect. Only the site root redirects to https.- cpan.metacpan.org serves plaintext with no redirect and sends no HSTS header.
- CPAN.pm falls back to
http://cpan.org/with a warning when it cannot load SSL modules. - That fallback is the default experience on two mainstream platforms. Homebrew perl 5.44 ships without IO::Socket::SSL, so
HTTP::Tiny->can_sslfails. Ubuntu’sperlpackage (noble 5.38, questing 5.40) neither depends on nor recommendslibio-socket-ssl-perl, and the noble and questing server cloud images contain perl but not the SSL modules.
- cpanminus 1.7049 (March 2026) still defaults to
http://www.cpan.organd issue 611 from June 2020 is still open. App::cpm defaults tohttps://cpan.metacpan.orgwith certificate verification. (App::cpm)
The CVEs that proved the point
- CVE-2020-16154 (cpanminus), CVE-2020-16155 (CPAN::Checksums), CVE-2020-16156 (CPAN.pm), disclosed 2021-11-23. An attacker who could serve a CHECKSUMS file could prepend malicious entries before the PGP-signed block and every client accepted it, because they only checked that some valid signed cleartext existed somewhere in the file. Any PAUSE user could also sign CHECKSUMS for another author’s namespace. (Hackeriet writeup)
- The official mitigation was “use an HTTPS mirror,” because signature verification was optional and off by default. That is the 2020 doc’s argument, restated by the CVE.
- cpanminus responded by removing CHECKSUMS-signature verification entirely in 1.7045 (January 2022) rather than fixing it.
What happened to signing
- Timeline, in order:
- 2020-06-25: my PR updating the bundled PAUSE and ANDK keys merged into Module::Signature 0.84 the day the doc was written. (PR 27)
- 2020: PRs to put Module::Signature in Bundle::CPAN and default
check_sigson (#137, #139) opened. - 2021-11-26: request for PAUSE to let authors upload their signing keys (andk/pause#377). Still open, zero comments.
- 2022-02-01: PAUSE dropped SHA-1 and MD5 from CHECKSUMS in favor of SHA-256 (andk/pause#352). Live CHECKSUMS files now carry sha256 fields under a SHA512 PGP digest.
- October and November 2023: briandfoy and stigtsp argued on #137 that a SIGNATURE file has no trust root, binds no distribution name or version (so it can be replayed onto a different tarball), and requires unpacking the archive before verifying anything.
- 2024-09-13: Module::Signature 0.89 replaced the dead
pool.sks-keyservers.netdefault with keyserver.ubuntu.com. The dead default shipped for about three years. - 2026-03-03: I closed #137 and #139.
- 2026-04-27: Module::Signature 0.96 marked DEPRECATED at the Perl Toolchain Summit in Vienna, with Audrey Tang’s consent. Authors are told to remove SIGNATURE files; users are told to uninstall the module. (Module::Signature 0.96)
- The 2020 doc’s own sentence about signatures proving only that a package “was signed by someone whose public key is retrievable” is the seed of the argument that killed it. Say so.
- Numbers: 2,569 of 41,827 latest releases (6.1 percent) ship a SIGNATURE. CPAN 2.38’s own SIGNATURE verifies against an expired key; Data::Float 0.015’s signer key is not retrievable at all.
- The PAUSE signing key itself: an RSA-4096 subkey rotated yearly under a DSA-1024 primary from 2003, published only in an HTML page. A rotation in June 2026 broke a downstream verifier. (andk/pause#592)
Where Perl stands next to everyone else
- Go: checksum database verified by default since Go 1.13. (sumdb)
- npm: trusted publishing with provenance attestations generated automatically. (docs)
- PyPI: Sigstore-backed attestations on by default for trusted publishers since late 2024. (announcement)
- crates.io and RubyGems: trusted publishing shipped. (crates.io, RubyGems context)
- CPAN: HTTPS by default, achieved with a fallback. Verification by default, not achieved, and the mechanism it would have used is deprecated. Behind on two axes in 2020, behind on one in 2026.
What is unowned
- CPANSec formed at the 2023 Toolchain Summit, became the CVE Numbering Authority for perl and CPAN on 2025-02-25 (47 CVEs in year one), maintains the advisory database behind CPAN::Audit, ships CycloneDX SBOM tooling, and won a $250k Alpha-Omega grant in July 2026. No signing or transport work; no published position on either. (2025 retrospective)
- One Sigstore prototype exists: a Dist::Zilla plugin that runs
cosign sign-bloband uploads a bundle beside the tarball. About 31 bundles across two authors. PAUSE does not verify them and no client checks them. (announcement) - PAUSE two-factor auth: requested in 2018, still open. (andk/pause#292)
- David’s view on what a PAUSE-side trusted-publishing design would need, and who could own it. Not drafted here.
Appendix: the 2020 claims against 2026
| 2020 claim | 2026 reality |
|---|---|
| CPAN.pm fetches over plaintext HTTP by default | HTTPS default since CPAN.pm 2.30 (Dec 2021); falls back to http with a warning if SSL modules are missing |
check_sigs defaults to 0; Module::Signature not in Bundle::CPAN |
Unchanged in CPAN 2.38, and moot: Module::Signature deprecated April 2026 |
| HTTP::Tiny does not verify certificates by default | Verifies since 0.083 (June 2023); in core from perl 5.40 |
| cpanminus uses HTTP and does not verify | Still http://www.cpan.org by default; CHECKSUMS-signature verification removed in 1.7045; issue 611 open |
| IO::Socket::SSL, Mozilla::CA, Module::Signature not in core | Still not in core in perl 5.44; Homebrew and Ubuntu perl ship without SSL modules |
| Author signatures rare, SHA-1, keyserver flaky, no PAUSE-vouched keys | SHA-256 default since 2018; 6.1 percent adoption; dead keyserver default until Sept 2024; author-key request open since 2021; module deprecated |
| Signed modules fail verification | Still true: CPAN 2.38 signed with an expired key; Data::Float 0.015 key not retrievable |
| PAUSE CHECKSUMS uses SHA-1 | SHA-256 fields under a SHA512 digest since Feb 2022; primary signing key is DSA-1024 from 2003 |
| 192 mirrors, about half with working TLS | Mirror network retired Feb 2021; MIRRORED.BY is a one-entry stub; deep paths on cpan.org still served over plaintext |
| Broken-mirror list of 30 hosts | 14 spot-checked: 7 valid certs, 3 NXDOMAIN, 2 TLS failures, 2 timeouts; survivors 404 on CPAN paths |
| FTP is a supported transport | Deprecated June 2022; ftp.cpan.org port 21 does not answer |
| Issue 118 and PRs 119 (May 2018), 135, 137, 139 | 118, 119, 135 closed as resolved or moot in September 2026; 137 and 139 closed unmerged March 2026 |
Not verified: when CPAN::Checksums first emitted sha256 fields (predates its Changes file), and when PAUSE switched the CHECKSUMS PGP digest to SHA512.
Sources
- CPAN mirror list changes, Perl NOC, 2021-02-22
- ftp.cpan.org deprecation, Perl NOC, 2022-06-11
- CPAN signature verification vulnerabilities, Hackeriet, 2021
- CPAN.pm Changes
- HTTP::Tiny Changes
- Module::Signature 0.96 deprecation notice
- Signing CPAN releases with Sigstore, Timothy Legge, 2026-05-07
- CPANSec 2025 retrospective
- CPANSec 2024 retrospective
- cpanpm PR 119, PR 135, PR 137, PR 139
- module-signature PR 27
- pause issue 352, issue 377, issue 292, issue 592
- cpanminus issue 611