This Week in Changelogs: curl
Hey everyone, long time no see!
I started TWiC in 2023, and to be honest, mining diffs manually was exhausting; that's why the project faded away pretty quickly. Today, with a little bit of LLM help and automation, it has become much easier to find hidden gems in modern OSS and bring them to the audience. There's another problem though: sometimes there are too many gems, and I definitely don't want to restart a series of boring longreads.
So today, we're gonna cover recent changes in only one project, (arguably) the most popular library and command-line tool in the world - curl.
Zip bomb protection via delivered-bytes tracking
A zip bomb is a relatively small compressed piece of data that is automatically decompressed on the client side into a giant blob, causing a DoS. One of the ways to protect against it is to set a limit on the incoming data size. Curl has had an option for it, CURLOPT_MAXFILESIZE, since 2003.
There was one small problem: before 8.20.0 it only affected the number of downloaded (compressed) bytes. The good news is that now they calculate delivered (decompressed) bytes separately, extending CURLOPT_MAXFILESIZE to act as zip bomb protection:
/* check that the 'delta' amount of bytes are okay to deliver to the
application, or return error if not. */
CURLcode Curl_pgrs_deliver_check(struct Curl_easy *data, size_t delta)
{
if(data->set.max_filesize &&
((curl_off_t)delta > data->set.max_filesize - data->progress.deliver)) {
failf(data, "Would have exceeded max file size");
return CURLE_FILESIZE_EXCEEDED;
}
return CURLE_OK;
}
Btw, check out the pull request itself: five AI bots and zero people besides the author (Daniel himself) were involved in the code review! And it actually helps a lot, because, well, check out the next one:
API bitmask copy-paste bug

CURLMNWC_CLEAR_DNS and CURLMNWC_CLEAR_CONNS were both defined as (1L << 0) (or just 1), so you could never clear just DNS or just connections - always both.
The bug was introduced mid-2025 (PR, diff), and despite having 3 people involved as code reviewers and 2 approvals, it made it into 8.16.0 to 8.19.0:
Luckily, Codex Security found it, and the author of the original changeset came up with an elegant solution, introducing CURLMNWC_CLEAR_ALL as the new (1L << 0) (since that's what every existing caller was unknowingly doing), and reassigning DNS and CONNS to bits 1 and 2:
if(val & CURLMNWC_CLEAR_ALL)
/* In the beginning, all values available to set were 1 by mistake. We
converted this to mean "all", thus setting all the bits
automatically */
val = CURLMNWC_CLEAR_DNS | CURLMNWC_CLEAR_CONNS;
So yeah, if you can afford to involve complex static analyzers or modern AI-reviewers, you'd better do that, because even seasoned lead devs can miss such small issues.
Missing return
Long story short, there was a helper called return_quote_error(). Thanks to its misleading name, the actual return statement was forgotten before the call, causing segfaults.
This is a classic case of a fix so small you're 100% sure you're right, you skip the review and just merge broken code:
I personally like that apart from fixing this problem, they removed the return_ prefix from the function name to avoid future confusion. Of course, such problems will also be caught by AI checkers, but here's another story:
PowerPC64 endianness AI-slop
A PR from a user named AutoJanitor with a robot avatar and shitloads of contributions since the end of January 2026 added __powerpc64__ to the list of architectures that can use unaligned memory access for MD5/MD4 fast paths with the following description:
Currently the fast path only covers
__i386__,__x86_64__, and__vax__. PowerPC64 (both LE and BE) (sic! - @dima) supports efficient unaligned memory access and should use the same optimization.<...>
Verified on IBM POWER8 S824 (ppc64le) — unaligned 32-bit loads work correctly and produce single
lwzinstructions.
To understand what it means, let's check out the code itself:
/*
* SET reads 4 input bytes in little-endian byte order and stores them
* in a properly aligned word in host byte order.
*
* The check for little-endian architectures that tolerate unaligned memory
* accesses is an optimization. Nothing will break if it does not work.
*/
#if defined(__i386__) || defined(__x86_64__) || \
defined(__vax__) || defined(__powerpc64__)
/* NB ^^^^^^^^^^^^^^^^^^^^^^ */
#define MD4_SET(n) (*(const uint32_t *)(const void *)&ptr[(n) * 4])
/* ... */
So this is an optimization for little-endian architectures, and, as pointed out later, causes undefined behavior.
Anyway, the contribution looked rock-solid, got merged, and a few days later, a real person (the guy has a website with /~sam/ - he knows his shit) came into the original PR, a discussion happened, and the commit was reverted.
The thing is that while PowerPC can run in either endianness, the preprocessor check __powerpc64__ doesn't tell you which one. The optimization above is exclusively for little-endian cases (casting byte arrays directly to uint32_t *, causing unaligned memory access), so on big-endian PowerPC64 it produced wrong results or just failed.
Unfortunately, AI bots have once again ruined things slightly for Daniel Stenberg. Apparently, that's the fate of popular projects these days ¯\_(ツ)_/¯
Thank you, folks! I hope you enjoyed this "episode" of This Week in Changelogs! Next time, fewer AI slop problems and more things to actually learn from.
This Week in Changelogs: flask, pytest, IPython, etc
This Week in Changelogs: Django and faker
Django 4.1.6, 4.1.7
- 4.1.6: release notes, blog post
- 4.1.7: release notes, blog post
9d7bd5a
An interesting bug of parsing the Accept-Language header. The format of the header value is complex, so there's a bunch of regular expressions and @functools.lru_cache(maxsize=1000) for caching the result. However, you can pass a huge header multiple times, causing DoS, so they added two if statements:
- one that checks if the length is less than
ACCEPT_LANGUAGE_HEADER_MAX_LENGTH - second - for checking the comma-separated strings. So they decided not to just raise an exception or truncate the string by
[:ACCEPT_LANGUAGE_HEADER_MAX_LENGTH], but truncate the value in a safe way, so it can be parsed in a meaningful result. Good job!


