Debian 11 (“bullseye”), which
has reached the end of its Long Term Support on 31 August 2026, will
now get security support from Freexian under the
Extended LTS offer.
The team published several notable updates:
chromium
(DLA 4710-1),
uploaded by Emilio, to fix 375 security flaws in bookworm, including
issues that could lead to arbitrary code execution.
Further DLA 4728-1,
uploaded by Emilio as well, fixed additional 41 security issues in bookworm and
DLA 4739-1
fixed 5 issues.
Last but not least,
DLA 4749-1
fixed another 15 issues and
DLA 4758-1
fixed another 7 issues.
ruby2.7
(DLA 4716-1),
uploaded by Abhijith, to fix four security flaws in bookworm, including
issues that could lead to arbitrary code execution.
several DLAs
(DLA 4717-1,
DLA 4720-1,
DLA 4723-1,
DLA 4724-1,
DLA 4745-1)
have been uploaded by Ben and Emilio to fix myriad security flaws in
different versions of the linux kernel
(5.10.262-1, 6.1.180-1, 6.12.100-1~deb12u1, 6.12.101-1~deb12u1)
in bullseye and bookworm.
7zip
(DLA 4718-1)
and p7zip
(DLA 4719-1),
uploaded by Sylvain, to fix multiple security flaws in bookworm and
bullseye, including issues that could yield to remote code execution.
ca-certificates
(DLA 4726-1),
uploaded by Bastien to add some new trusted certificates to bookworm and bullseye,
but also remove some untrusted ones.
thunderbird
(DLA 4727-1
and DLA 4754-1),
uploaded by Emilio to fix 35 and 31 issues in bookworm and bullseye.
nss
(DLA 4729-1),
uploaded by Jochen to fix a vulnerability in bookworm and bullseye that could
yield to arbitrary code execution.
libgd2
(DLA 4731-1),
uploaded by Guilhem to fix a vulnerability in bookworm and bullseye that could
yield to arbitrary code execution if a malformed GIF file is processed.
xorg-server
(DLA 4737-1
and DLA 4738-1),
uploaded by Arnaud to fix several vulnerabilities in bookworm and bullseye.
postgresql-15
(DLA 4740-1),
uploaded by Carlos Henrique Lima Melara to fix 26 vulnerabilities
in bookworm.
unzip
(DLA 4741-1),
uploaded by Andrej to fix a vulnerability in bookworm and bullseye
that could yield to arbitrary code execution.
firefox-esr
(DLA 4750-1),
uploaded by Emilio to fix 31 vulnerabilities in bookworm and bullseye
that could potentially result in arbitrary code execution, privilege escalation
or information disclosure.
nvidia-graphics-drivers
(DLA 4752-1 and
DLA 4753-1),
uploaded by Tobias to fix lots of vulnerabilities in bookworm and bullseye.
roundcube
(DLA 4760-1),
uploaded by Guilhem to fix ten vulnerabilities in bookworm and bullseye
that could potentially result in remote code execution or information disclosure.
Contributions from outside the LTS Team:
We are greatly thankful for the contributions from people outside the LTS Team:
Salvatore Bonaccorso released a libyaml-syck-perl security update
(DLA 4730-1),
to fix a denial of service and potentially arbitrary code execution.
Thomas Goirand released a neutron security update
(DLA 4735-1).
Thomas Goirand released an ironic security update
(DLA 4743-1).
Thomas Goirand released a designate security update
(DLA 4751-1),
to fix a denial of service or DNS hijacking.
The LTS Team has also contributed with updates to the latest Debian releases:
xrdp
(DSA 6469-1),
prepared by Abhijith to address privilege escalation and arbitrary
code execution related flaws.
Other contributions:
Besides the work on security updates, different documentation and tooling
changes were needed. This work was mainly done by Sylvain.
In 2016, I removed Google Analytics from this blog to avoid being
complicit in feeding the biggest machine for harvesting personal data. Instead,
I relied on GoAccess to analyze my server logs.1 For the past couple
of years, the statistics have made no sense, despite my attempts to filter bots:
AI scrapers inflate the number of visitors to around 2,000 per
day. Eventually, I settled on GoatCounter, an open-source, privacy-friendly
web analytics platform. I replaced the JavaScript client to filter bots more
aggressively and added a CSS fallback. To improve reliability, I implemented a
local proxy running on each of the five web servers serving this blog. The rest
of this post details how these pieces fit together and how I deploy them on
NixOS. ❄️
GoatCounter does not collect personal data: instead of storing the reader’s
IP address or relying on cookies, it creates a session identifier valid for 8
hours from the user agent and the IP address. Its feature set is modest but
sufficient for a blog. If you want to look at the interface, GoatCounter’s
author runs a public instance for his site. A hosted
version lets you try it before running your own instance. With a single binary
and an SQLite database, GoatCounter is one of the lightest self-hosted
solutions. Privacy-friendly alternatives, in increasing order of complexity,
include Umami, Plausible, and Rybbit.
Custom JavaScript client
GoatCounter includes a small JavaScript client—2,189 bytes minified and
gzipped. It ships some features I don’t use: a visitor counter, tracking clicks,
configurable settings, etc. I replace it with this function to register a hit:
If a reader has disabled JavaScript in their browser, I record the hit using a
CSS image. The :hover pseudo-class loads it only after an interaction, another
trick stolen from Bear Blog. About 2% of my visitors fit into this
bucket.3
<!DOCTYPE html><htmllang="en"class="nojs"><head><script>// The JavaScript code for this blog requires ES6if("noModule"inHTMLScriptElement.prototype)document.documentElement.classList.remove("nojs");</script></head><body><!-- ... --><style>.nojsbody:hover{border-width:0;border-image:url('/count?p=/en/blog/2026-kpi-goodhart&t=Building...&r=NoJS&e=false');}</style></body></html>
Where GoAccess reported around 2,000 visitors a day, GoatCounter counts fewer
than 200 humans.4 I assume AI scrapers use a low-effort approach: if the
content is available without barriers, as on this blog, they don’t spawn a
complex mechanized browser that could trigger a page view. Even crawlers running
JavaScript, like Googlebot with its headless Chromium, do not
interact with the page and never trigger the events I listen
to. The interaction-based “proof of humanity” I use is likely to keep
working.5
Local proxy
Five servers across the world in Europe and in North
America serve the content of this website, but GoatCounter runs on only
one of them. To avoid losing track of visitors when GoatCounter is down, I run a
local proxy listening on the same /count endpoint. On each server, it stores
the hits in memory with a buffer large enough to survive several days of
downtime. It sends them in batches to the upstream backend using the
/api/v0/count authenticated endpoint.
🖼 Servers on a map. web02 is in Paris, web03 in Helsinki, web04 in Nuremberg,
web05 in Ashburn, web06 in Chicago.
I proposed the code for the proxy in pull request #909. GoatCounter’s
maintainer declined to maintain so much code for such a niche use case. As a
fellow open-source developer, I often hold the same position for my own
projects: a one-time contributor effort may translate into a long-term
maintainer commitment.
I expose the endpoint for the proxy on the domain of this website to evade ad
blockers. This sounds like I don’t respect the reader’s choice, but as
GoatCounter is privacy-friendly, I find it acceptable.
My web servers run NixOS, a declarative Linux distribution with built-in
configuration management. I manage this small fleet with Colmena, a
stateless deployment tool for NixOS. My configuration is available on
GitHub.
Deploying applications in containers
For better isolation, each application runs inside an ephemeral lightweight
container, powered by systemd-nspawn. Each container runs a stripped-down
NixOS instance. A module wraps NixOS’s containers options to
avoid repeating the same options for each application.6 The containers
share their network namespace with the host: the additional isolation is not
worth the increased complexity. For a smaller footprint, I also disable a few
non-essential services.
{ config, lib,...}:letcfg= config.luffy.containers;in{# User-configurable settings for our custom module
options.luffy.containers= lib.mkOption {default={};description="Ephemeral containers sharing the host network.";type= lib.types.attrsOf (lib.types.submodule {options={config= lib.mkOption {type= lib.types.deferredModule;default={};description="NixOS configuration of the container.";};};});};# Translate our options to NixOS containersconfig={containers= lib.mapAttrs
(name: container:{ephemeral=true;autoStart=true;privateNetwork=false;extraFlags=["--resolv-conf=replace-host"];config={imports=[ container.config ]; networking.firewall.enable=false;
system.stateVersion= config.system.stateVersion;
systemd.services={ console-getty.enable=false; systemd-logind.enable=false; systemd-oomd.enable=false;};};})
cfg;};}
To configure a GoatCounter instance running in a container and listening on
127.0.0.4:8088, we import the module7 and declare the container in the
config.luffy.containers attribute set:
As the containers are ephemeral, we need to keep persistent data in directories
on the host. We add a mounts option and ask NixOS’s containers to expose the
configured directories through the bindMounts option.
{ config, lib,...}:letcfg= config.luffy.containers;in{
options.luffy.containers= lib.mkOption {type= lib.types.attrsOf (lib.types.submodule {options={mounts= lib.mkOption {type= lib.types.listOf lib.types.str;default=[];description="Host directories mounted read-write at the same place.";};};});};config={containers= lib.mapAttrs
(name: container:{bindMounts=
lib.genAttrs container.mounts (path:{hostPath= path;isReadOnly=false;});})
cfg;};}
For example, to persist GoatCounter’s database in the /var/db/goatcounter
directory on the host, we add the directory to the mounts option and alter the
service definition to tell GoatCounter where the database is.
A container may also need some secrets. Colmena can upload secrets without
storing them in the Nix store. We add a keys option to our containers. It
takes an attribute set mapping secret names to the commands to populate them.
Then, the module declares the required secrets to Colmena in the
deployment.keys option, makes the container depend on the presence of the
secrets, and exposes them to the container.
{ config, lib,...}:letcfg= config.luffy.containers;in{
options.luffy.containers= lib.mkOption {type= lib.types.attrsOf (lib.types.submodule {options={keys= lib.mkOption {type= lib.types.attrsOf (lib.types.listOf lib.types.str);default={};description="Secrets, as a command to run locally. They are mounted in /etc.";};};});};config={# Colmena uploads each secret in `/var/keys` and make them available# to the group "keys".
deployment.keys= lib.concatMapAttrs
(_: container: lib.mapAttrs
(_: keyCommand:{inherit keyCommand;group="keys";permissions="0640";destDir="/var/keys";})
container.keys)
cfg;# The container can only start if the required secrets are available.
systemd.services= lib.mapAttrs'
(name: container:letunits=map(key:"${key}-key.service")(lib.attrNames container.keys);in
lib.nameValuePair "container@${name}"{requires= units;after= units;})
cfg;# Mount each secret inside the container.containers= lib.mapAttrs
(name: container:{bindMounts= lib.mapAttrs'
(key: _: lib.nameValuePair "/etc/${key}"{hostPath="/var/keys/${key}";isReadOnly=true;})
container.keys;})
cfg;};}
For example, GoatCounter needs credentials to download the GeoIP database. I
provide a local command to fetch the secret from my password manager and expose
it inside the container through the /etc/goatcounter.env environment file.
I wrote a NixOS module to encapsulate GoatCounter: the
container definition, the service definition, and the secrets. The module
accepts the following options: package, serve.enable, serve.listenAddress,
serve.port, and serve.databaseFile. I already detailed the container
configuration in the previous section. In the end, I chose not to reuse the
GoatCounter module from NixOS: it’s small, so it’s better to insulate my module
from unexpected future changes.
{ config, pkgs, lib,...}:letcfg= config.luffy.goatcounter;databaseDirectory=builtins.dirOf cfg.serve.databaseFile;chown="${pkgs.coreutils}/bin/chown -R";in{
config.luffy.containers.goatcounter={
config.systemd.services.goatcounter={description="GoatCounter Web Analytics";wantedBy=["multi-user.target"];serviceConfig={EnvironmentFile="/etc/goatcounter.env";SupplementaryGroups=["keys"];DynamicUser=true;Restart="always";ExecStart= lib.escapeShellArgs [(lib.getExe cfg.package)"serve""-listen=${cfg.serve.listenAddress}:${toString cfg.serve.port}""-tls=none""-db=sqlite+${cfg.serve.databaseFile}""-automigrate"];# Transfer database ownership to dynamically assigned user "goatcounter".ExecStartPre="+${chown} goatcounter:goatcounter ${databaseDirectory}";ReadWritePaths= databaseDirectory;};};};}
The following snippet configures GoatCounter to listen on 127.0.0.4:8088:
The same NixOS module configures the local proxy, with the
following options: proxy.enable, proxy.listenAddress, proxy.port, and
proxy.site—the site receiving the batches of page views. The local proxy has
no persistent data, but it needs the API key to authenticate to the main
GoatCounter instance: its container uses the keys option but not the mounts
option.
{ config, pkgs, lib,...}:letcfg= config.luffy.goatcounter;keyCommand= _:["…"];in{
config.luffy.containers.goatcounter-proxy={ keys."goatcounter-proxy.env"= keyCommand "GOATCOUNTER_API_KEY"; config.systemd.services.goatcounter={description="GoatCounter Proxy.";wantedBy=["multi-user.target"];serviceConfig={EnvironmentFile="/etc/goatcounter-proxy.env";SupplementaryGroups=["keys"];DynamicUser=true;Restart="always";ExecStart= lib.escapeShellArgs [(lib.getExe cfg.package)"proxy""-site=${cfg.proxy.site}""-listen=${cfg.proxy.listenAddress}:${toString cfg.proxy.port}""-ratelimit=10/1"# 10 requests per second per IP];};};};}
For each server, I enable the local proxy with the following snippet. The
nginx configuration shown earlier exposes the /count endpoint under the same
domain as my blog.
Litestream is a streaming replication tool for SQLite databases. It
compresses the changes committed to the write-ahead log (WAL) next to the
database and sends them to a remote destination. I encapsulate its configuration
in a NixOS module, which takes an attribute set databases
mapping a name to the path of the database to back up.
Litestream also runs in a container. I mount the databases to replicate, as well
as the secrets to push the backups to a Hetzner storage box using SFTP:
We can restore the database from the backup with a few shell commands. First, we
stop the containers. Then, we move the damaged database away, invoke litestream
restore from the right environment, and restart the containers.9
Ten years after removing Google Analytics, JavaScript-based analytics is
back on this blog, but without storing cookies or IP addresses, and without
involving a third party. I still write for myself first, notably because it lets
me dig into a topic and refer back to it years later. But knowing a bit more
about my fellow human readers is a nice bonus, even the ones disabling
JavaScript. 🐐
Nginx scrambles IP addresses before storing them, thanks to the ipscrub
nginx module. ↩
GoatCounter already filters some bots based on the user agent or the IP
address. But AI scrapers lie about their user agent and hide behind
residential proxies. ↩
Without JavaScript, I cannot send the real referrer. I insert “NoJS”
instead. ↩
I am missing the humans reading the RSS feed. I am not comfortable adding a
tracking pixel, and bots are likely to fetch it, compromising the
statistics. I’ll live without counting these readers. ↩
In February 2025, I tested another CSS-only approach without the interaction
trick: each page loaded an empty image as a background. Over a month, it
recorded up to ten times fewer page views than the nginx logs, even after
removing user agents identifying as bots. Compared to this experiment,
GoatCounter counts about three times fewer views, 18 months later. As the
time ranges differ, this is not an apples-to-apples comparison, but it gives
a hint about how efficient bot filtering is. ↩
A NixOS module is a function receiving the configuration of the whole
system as config and returning three attributes:
imports adds other modules to import,
options declares user-configurable settings, each with a type and a default value, and
config sets values for options declared by any module, like containers from NixOS.
If a module does not need to declare options, you can return the config attribute
set directly. When a module does not require any argument, you can define it as an attribute
set instead. ↩
Most of the time, you don’t need an explicit import. NixOS automatically
imports the modules shipped with Nixpkgs. For my own
modules, some machinery also imports them automatically. ↩
Litestream warns against using the auto-recover option as
it can cause data loss. But I don’t properly monitor my servers and I prefer
uninterrupted backups to a slight chance of losing the last few records. ↩
In my case, the process is slow: around 20 minutes for a 20 MiB database.
You can test by restoring to a copy with the -o option, but you still need
to stop the Litestream container. ↩
The Debian Long Term Support (LTS) Team hereby announces that Debian 11
bullseye support has reached its end-of-life today, 31 August 2026,
five years after its initial release on 14 August 2021.
Starting in September, Debian will not provide further security updates for
Debian 11. A subset of bullseye packages will be supported by external
parties. Detailed information can be found at
Extended LTS.
The Debian LTS Team is currently providing security support for Debian 12
bookworm, the current oldstable release. Thanks to the combined efforts
of different teams including the Security Team, the Release Team, and the LTS
Team, the Debian 12 life cycle encompasses five years.
Debian 12 will receive Long Term Support until 30 June 2028. The supported
architectures in Debian 12 LTS are amd64, i386, arm64, armhf and ppc64el.
For further information about using bookworm LTS and upgrading from
bullseye LTS, please refer to
LTS/Using.
Debian and its LTS Team would like to thank all contributing users, developers,
sponsors and other Debian teams who are making it possible to extend the life
of previous stable releases, and who have made Bullseye LTS a success.
pcsc-lite is a Free Software
implementation of the PC/SC
(also known as WinSCard) API for Unix systems. It provides an API for
using smart cards and
smart card readers.
Changes:
2.5.2: Ludovic Rousseau
19 September 2026
hotplug_libudev: rescan "serial" readers in
HPReCheckSerialReaders()
After bazel-bootstrap 7.7.1 was landed into Debian unstable,
I'm working on packaging newer Mozc (Most famous Japanese input method editor) with Bazel.
Here is the blog entry initial efforts to build Mozc with Bazel at that time.
It is impossible to cover all of system libraries in Debian, so in that case, please consider to use the following DH_BAZEL_PKGCONF_MODULE.
DH_BAZEL_PKGCONF_MODULE
It search the specified modules with pkgconf. It is useful when there is no bundled modules in dh-bazel if you want.
It accept ',' separated paramesters. (e.g. DH_BAZEL_PKGCONF_MODULE=gtk4-x11,gtk4-unix-print).
If DH_BAZEL_PKGCONF_MODULE could not match, then dh-bazel fallback to dig into Build-Depends: field
in debian/control. Note that if it is not deterministic (e.g. -dev package provides multiple .pc files)
dh-bazel gives up fallback with pkgconf.
In version 1.5.19, I upgraded
the PKCS#11 header file pkcs11.h to version PKCS#11 3.2.
Execution of automatic tests failed on Windows. I then disabled
failed test. But I, in fact, disabled all the tests on
Windows. And the problem went unnoticed.
Version 1.5.19 did not contain the code required
to pack the structures on Windows. So the PyKCS11 wrapper and the
PKCS#11 library were not alligned on the data format. And crash...
version 1.8.3 of the CCID driver (New version of libccid: 1.8.3)
addresses 6 security issues identified by an AI tool.
Very low impact
The reported issues are either not exploitable because the input and
output buffers used by PC/SC Lite to call the CCID driver are large
enough, or the exploitation requires rogue smart card or smart card
reader (i.e. sending non-compliant data).
If you would like to read the details, the issues have been fixed in the
following git commits:
However, it would be difficult to exploit these issues unless the
attacker used a custom-built reader or smart card. This is something I
started doing with the Pico HSM project, which is a CCID
reader in a Raspberry Pi Pico. This allows you to modify the CCID frames
sent by the reader as required.
The bug reports from the AI tool are very verbose. This is useful for
impressing a manager with a long, complex-looking text. However, reading
the entire report is often a waste of my time. It is often much faster
to read the proposed patch to understand the problem it is trying to
fix.
The proposed fixes are, sometimes, incorrect. They are a good starting
point, though. However, never apply an AI-generated patch without fully
understanding it.
Conclusion
Thanks to Red Hat and Jakub Jelen for
the bug reports.
I am very happy that the AI tool only identified issues with no or low
impact.
As a graduate of a liberal arts university, I wound up, unsurprisingly, taking a
lot of classes in every possible academic discipline. Thinking back to the
person that I was going into university, I don’t think I would have chosen to
take them – after all, my degree was in the sciences; I’d have been stoked to
do nothing more than wall-to-wall computer science until I ran out of
coursework and filled the rest of my hours with research and independent
studies with my professors (which, I guess I did actually do, just not as
much as I would have otherwise).
Even this blog’s CSS theme (now 18 years old and starting to look it) was
something I wrote after receiving and repeatedly re-reading a tattered
third-hand copy of “The Laws of Thought” by George Boole. It was gifted to me
by a college friend majoring in philosophy while I was crashing at his rental
on the beach. He gave it to me because he knew I “liked that shit” and, while
it was definitely computer science in nature, I would not have read it
otherwise. I have vivid memories of sleeping on his la-z-boy surrounded by
towers of books he was working through. He went on to do incredible work,
getting his PhD, doing research, brilliant writing – his death in 2023 has
robbed humanity of more time with him. “The Laws of Thought” sits behind me in
my office, and is one of my most valued possessions.
My friends mean more to me than I could ever express, and I definitely don’t
show it enough. Each of my classes made me a more thoughtful person. My
professors made me a better, more well-rounded person – and a person who
attempts to live up to the oft repeated credo of being “men and women for
others”. I did the best I could, even though I was never a particularly good
student. Without liberal arts, I don’t think I would, dispositionally, have
been capable of pushing myself – to this day – to continue to learn on nights
and weekends, for no reason other than wanting to learn. I refuse to stop
expanding my perspective, and do my best to approach new problems with as much
humility and curiosity as I can muster.
Every few days for the last year or so, I have been thinking back to a reading
assignment from my junior year that, at the time, I thought was a borderline
throwaway filler assignment for the class. The reading is an essay from 1947 by
the french existentialist philosopher and libertarian marxist, Jean-Paul Sartre
(as some of the more erudite may have now already worked out given the blog’s
title), “Why Write?”. I re-read it this week. It is not filler. I remember this
essay better than some of the classwork I considered more important at the
time.
Why Write?
“Why Write?” starts off by describing the ways in which writing – trying to
communicate your thoughts, opinions, or feelings to others – is an act of
projection. The writer will only ever draw from a place of their “own
subjectivity” – writing is taking your person and putting it on display.
You’re choosing what words to use, how to use them, pulling from knowledge
you’ve accumulated (based on how you’ve chosen to spend your time). Spending
any amount of your finite existence in order to convey a thought is, itself,
announcing to the world that you believe it to be a thought worth sharing.
Meanwhile, the act of reading is not simply turning letters into words.
Reading is engaging with the work, and understanding the work in a process
that looks more like what Sartre terms “re-invention” or “discovery” – “the
literary object, though realized through language, is never given in
language”. It is not enough to read the words in a book end-to-end; you must,
as a reader, actively engage with the work to understand what is being
communicated by those words. Reading is to take the words on the page, and
“exceed” the mere words through what he calls “directed creation”. The reader
is “re-inventing”/“discovering” the writer’s thoughts by following their
“landmarks in the void”.
And this all makes pretty good sense to me – I know if someone is being
sarcastic because I know the writer; I have read their words and have read
into their words, allowing myself to be directed by the writer into
“discovering” the thought they have left me in their work. I understand that
their words are humorous, not irate. Knowing who the author of a work is
can completely change the point of a sentence. The fact I’m writing about
Sartre at all, or that this very writing has been constrained for presentation
in this blog’s CSS is only happening because of who I am, what things I’ve
experienced in life, and what has made me, me. Sartre argues that this dyadic
coupling between writer and writing means that I, as a writer, can never truly
read my own writing. I will never be capable of reading my own work and
getting something out of it – the work is already an extension of my own
person. I can discover no thought in my own works.
Of course, Sartre, as an existentialist, is honor bound to go one step further
here – the reader, by participating in reading a work, is asserting their
own freedom. Every time a writer writes “[…] the writer appeals to the
reader’s freedom to collaborate in the production of [their] work”. The
creative act may only be complete if both writer and reader have recognized one
another and made a choice to do so. No one is compelled to complete the
creative act. The things you read matter. Reading something fundamentally
alters you as a person – you can not simply un-read a thought. Everything you
read changes your universe. I have a hunch Sartre would find things like the
(original early 2010s era) “tl;dr” reply to obviously terrible writing
absolutely hilarious as a reader’s expression of freedom (not to be confused
with modern 2020s era usage as shorthand for “summary section”). Using your
freedom to choose to complete the writer’s creative act (or not) is inherently
asserting your humanity.
I’m a bit fuzzy on the specifics of this quote, but I think it was
sj who told me at one year’s
Mystery Hunt that “A puzzle is a contract between
puzzle author and puzzle solver, the author promises that the puzzle is
solvable if you’re clever enough”. The puzzle author and puzzle solver are
engaging in a collaborative production of the work that is only possible by
recognizing one another. Similarly, Sartre – “whatever connections [the
reader] may establish among the different parts of the book among the chapters
or the words [the reader] has a guarantee, namely, that they have been
expressly willed”.
Wall Drawing #123
It follows, Sartre argues, that the act of writing, as a means to convey a
thought to a reader, may only be completed through the act of reading.
Reading can only be done by others; writing, therefore, only exists to be
read – it can serve no other purpose. Writing and reading are two halves of
the same creative, collaborative act, only satisfied when both writer and
reader acknowledge one another. Combined, writing and reading are the act of
recognizing one another’s humanity, thoughts, experiences, consciousness – the
act of co-creation of thought is the critical aspect of writing and reading –
reconstructing the author’s perspective, thought, intent, point. The
co-creation is the point of both reading and writing.
Writing without anyone to read the work leaves the writer’s bid for
co-creation unsatisfied and humanity unrecognized. No thought has been conveyed
to any reader, no one has understood the reason behind the “landmarks in the
void” you’ve carefully placed, leaving you to either “[…] put down [your] pen
or despair”. Writing is an appeal to the reader’s freedom, and the nature of
that freedom is the writer can not control it – never being understood is
always a possibility any time anyone sets out to write.
Nearly 12 years ago, I attempted to read the text on the git manpage
generator for the first time –
I remember the exact feeling of my brain going into “git manpage parsing
mode” where every word was dragging git plumbing megaliths through the sand
from their far-flung homes. I can still feel the surface of my desk as I
instinctually started to trace out logical connections between git internals
referenced as I read along. It took me a good 20 seconds to realize what I was
reading made no sense. This website broke Sartre’s writer-reader agreement – I
was attempting to read this website, but there was nothing there. You can
“mechanically” “read” the git-man-page-generator, but you can never read it
– it is not possible to read it. It was funny – unsettling. Each grasp at a
real-looking image in front of me misses, my hand coming back empty. I
couldn’t get enough of it. I must have tried to read a dozen generations in a
row. I had never had something quite that broken pass that far into my
consciousness before. Although I kept trying, it never did feel quite the same
as that first time; I think fundamentally, I couldn’t forget that it was
random.
While never having anyone read your writing drives them to “put down your
pen or despair”, Sartre never had to contend with the opposite problem – being
asked to read that which was not written. Reading that which was not
written does not merely leave someone unrecognized when a reader makes a
choice; instead, it is an inherent violation of the contract between reader
and writer. Not only is no humanity being recognized through attempting to
read words which were not written, but the reader, not the writer,
is the one who bears the burden of unexpected apophenia. The reader,
while engaging in co-creation, must now contort themselves to uphold their end
of the Sartrean agreement, scrutinizing text to ascribe consciousness,
thought and intent only to come back up with pareidolia-fueled echos of
one’s own self and disfigured half-thoughts of others. The reality is, the
modern reader must now “mechanically” “read” most written work they come
across, scrutinizing text for any signs of thought before truly attempting
to read the work. Failing to do this correctly changes our person –
each time it happens, a small part of us is irrevocably altered. Choosing to
engage in this ballet because you wish to do so is one thing – this is
your right and freedom as a reader – but passing words which were not
written as your own in an attempt to wrestle a reader’s freedom of
co-creation from them is another entirely.
If you didn’t write it, I don’t want to read it (dw;dr). Send me your prompt instead.
It is said that those who cannot remember the past are condemned to
repeat it, a quote often attributed to the American philosopher George
Santayana. And to remember the past, records must be maintained and
kept accessible to learn from. With this in mind, it is no wonder
that archivists worldwide consider it crucial to ensure the archival
records are both complete and accurate, with a constant frustration
caused by the knowledge that the archives are neither due to
challenges in tracking down and collecting what should be
archived.
The last few days, I decided to simplify the collection of Messages
from the Mattermost chat
service, used by a few of the organisations I am involved in, to try
to improve the situation slightly. To achieve this, I had written a
dedicated extraction tool. Partly to see how hard it would be, and
partly to ensure that if one of the services were shut down or
replaced, everything in it would not be lost. Of course it is in the
nature of the use of instant messages that most of them are not fit
for permanent storage, at least not according to Norwegian law, where
there is a threshold "worthy of the archive" (arkivverdig) that should
be met, and messages like "should we go to lunch now" are below the
bar and should be filtered out. The tool can not help with this
filtering, and that will have to be done using other means after
collection.
The tools I had my bullshit generator write, under strict
supervision and many iterations, will extract every message visible to
the user whose credentials are used to log into Mattermost, and write
it out in Noark 5 extraction XML format and visualize the result. I
created the visualiser mostly to quickly be able to debug the
extracted XML, but also to make life easier for anyone interested in
testing out the tool set.
The extractor mattermost-noark5extract create a hierarchy
with arkiv/arkivdel for the Mattermost server, and then individual
mappe for each channel and direct message chat, a registrering for
each message thread, dokumentbeskrivelse for every message in the
tread, and one or more dokumentobjekt for each message and their
attachments/images. So far it is only tested on one Mattermost
installation, where around 23,000 messages is extracted in 61 seconds
and produce 295M with approximately 600 attachments and around 675,000
lines of XML in arkivstruktur.xml. You pass it the URL of the
service, a username and password, a directory path where to store the
collection and an optional channel name substring to limit the
collection to only a subset of the messages available.
The mattermost-noark5extract-browser viewer can load this
collection and visualize it similarly to Mattermost's web interface,
with the list of channels and direct messages dialogues on the left,
messages chronologically in the centre and a selected thread displayed
on the right.
I know Mattermost provide several login options. I've only had the
one used on my test server implemented so far, and know the extract
tool will have to be extended a bit for it to handle servers using one
of these options.
This is a new series1 I'm starting on the tools I have been crafting and
buying to play pedagogical games in my Economics classes.
These games, aside from being just plain fun for the students, often aim to
teach them a lesson via interactive play. Unsurprisingly, it is much easier for
them to grok a particular concept this way. Moreover, many students told me
these activities were the best part of the classes I teach.
The World Trade Game
I found out about The World Trade Game in The Handbook of Pluralist Economics
Education2, which also contains many other fun pedagogical games. It
was first developed by Action Aid, but has since been refined by John
Sloman and then by Irene van Staveren, who wrote about her use of the game in
the aforementioned book.
The goal of this blog entry is not to describe the game and teach you how to
play it. If that interests you, either buy the book I mentioned or read about
Sloman's version here.
No, our focus point here is the tools to play this game: fake cash! Fake cash
can be used in many economics pedagogical games and in my opinion, is a
worthwhile "investment" of your time and energy. You could indeed play with
regular printed sheets of paper listing a value (I sure did!), but using Play
Money (a more appropriate term, since we're not trying to defraud anyone) will
up your game and create a more immersive atmosphere in your classes.
At first, I tried to buy play money. Surely, some commercial offering aiming to
create realistic props for movies and TV productions existed? At least in
Canada, it turns out the answer was no. The only thing I could find were shady
websites and weird expensive listings on AliExpress. Not being tenured yet, I
did not try to convince my employer to take a 50-50 chance on supporting
criminal endeavours.
As such, the next best thing I found was printable play money designed by the
Bank of Canada Museum. Their designs were quite nice and met my needs,
but their PDF layout made it overly complicated to print large quantities of
bills. As such, I played around with GIMP a little and came out with these
files. A commercial printer shouldn't have issues printing them on glossy
cardstock paper and cutting them up for you:
Once printed and cut, you'll unfortunately have to fold each bill by hand. To
secure both halves, I ended up using a strip of transparent tape on each long
edge.
Here is the result, with a real Canadian 20$ bill as a comparison:
Don't hold your breath though, as I do not currently have a release
schedule. ↩
Reardon, J. (2009). The Handbook of Pluralist Economic Education. New York : Routledge. ↩
After bazel-bootstrap 7.7.1 was landed into Debian unstable,
I'm working on packaging newer Mozc (Most famous Japanese input method editor) with Bazel.
Here is the blog entry initial efforts to build Mozc with Bazel at that time.
When started packaging efforts for newer Mozc with Bazel, I'm a newbie to do it.
Now I've got a knowledge to do it a bit, I want to know best practice on packaging X on Debian with Bazel furturmore.
Usually there are dh-X for buildsystem X on Debian, but it's not true for Bazel as far as I know.
This is why I had started to write dh-bazel.
I've wrote initial dh-bazel prototype and post a mail to debian-bazel ML.
dh-bazel is in very early stage prototype, but I have succeeded to build newer Mozc with some modifications to Mozc debian/rules on experimental with dh-bazel locally!
dh-bazel is a thin wrapper for Bazel, so it does not reduce packaging glitches dramatically, but it helps some sort of packaging tasks IMHO.
There are some achievement with dh-bazel
It helps to do similar building way like other debhelper buildsystem
It sets comprehensive Bazel startup options
It sets comprehensive Bazel command options
If you want to set extra flags, just specify it as additional user options (--override_module=, and so on)
Note that it only simplify dh_auto_build stage, so you must manually install artifacts
with .install or something correctly.
I hope that it will help package maintainer using Bazel in the future.
(dh-bazel is not uploaded into debian/unstable yet, so stay tuned!)
Nheko is my current favourite client for the Matrix IM system, which is my favourite IM system.
Matrix is an open system with end to end encryption and Nheko is free software and runs well on Linux desktops and phones.
The Nheko client allows interaction with dbus which could be good for automating things, EG you could change the status message when unlocking the screen. I’m documenting the most useful ones here because they don’t seem to be documented anywhere else. I have filed a Debian bug about the activate room option not working. The qdbus6 program is the QT6 version of the dbus command-line query program, there are a range of other programs which work in much the same way.
# list all interfaces
qdbus6 im.nheko.Nheko /
# get the version of Nheko
qdbus6 im.nheko.Nheko / im.nheko.Nheko.nhekoVersion
# list rooms in a dump of the data structures (pity it's not json or something)
qdbus6 --literal im.nheko.Nheko / im.nheko.Nheko.rooms|less
# join a room
qdbus6 im.nheko.Nheko / im.nheko.Nheko.joinRoom "#flounder-random:luv.asn.au"
# supposed to activate a room but doesn't
qdbus6 im.nheko.Nheko / im.nheko.Nheko.activateRoom "#flounder-random:luv.asn.au"
# set the status
qdbus6 im.nheko.Nheko / im.nheko.Nheko.setStatusMessage "whatever"
# get the status
qdbus6 im.nheko.Nheko / im.nheko.Nheko.statusMessage
Here are a couple of examples of using other dbus clients to get similar results. Note that the difference between the Debian version of Nheko (and maybe other recent versions) and what LLMs return for usage examples is that Debian has “/” as the path while the examples have “/im/nheko/Nheko”.
# list rooms via gdbus
gdbus call --session --dest im.nheko.Nheko --object-path / --method im.nheko.Nheko.rooms
# get status via dbus-send
dbus-send --session --print-reply --type=method_call --dest=im.nheko.Nheko / im.nheko.Nheko.statusMessage
Related posts:
dbus error Strange dbus error from the KDE power monitoring tool...
Hangouts Replacement Google is currently in the process of killing Hangouts. Last...
Matrix Introduction In 2020 I first setup a Matrix [1] server....
I’ve long been interested in hardware key material storage devices. I’ve been
a fan of yubikeys (I still remember
when my fancy new NEO-N showed up), PIV
(and its associated
smattering of
additionalfields),
SaaS HSMs,
the kernel keyring, some tooling
I’ve fairly satisfied with the design of at prior companies, and of course,
our dear friend, the TPM. All that is not even to mention the scores of exotic
hardware security modules one generally comes across from time to time when
you’re keeping a sharp eye out that you wind up playing with.
The concept of storing private key material on a disk, or even having it in
RAM has always skeeved me out, so I have a natural inclination to hardware
modules, and how shifting keying material around can change your risks
and threat model(s) in interesting ways.
I don’t remember when I first came across the
MIFARE DESFire EV3, but a few weeks ago
I did a deep-dive into the state of the art of authentication schemes using
ID cards. My complete overview of what tradeoffs exist is pretty extensive
(and likely not interesting to the vast majority of the world), but the tl;dr
wound up being one of “use PIV” or “use MIFARE DESFire EV3”. I wound up
picking DESFire for a recent project, and figured it’s worth talking
a bit about what I learned, share some thoughts, and some code. That code is
published on crates.io/desox, and docs,
as is our custom, may be found at
docs.rs/desox
DESFire supports DES (I’m sure most readers saw that one coming), 3DES (I
didn’t bother playing with 3DES at all) or AES-128 (AFAICT always use this?)
keying material. It’s worth noting that the DESFire only supports symmetric
keys and is not designed for public key cryptography, and operates exclusively
using shared symmetric key material. The DESFire EV series use those keys and
related authentication schemes to interact with “files” stored on the on-chip
EEPROM (2k, 4k, 8k, and 16k versions exist), or “applications” (groups of files
and authentication keys).
Talking to a DESFire EV3
Interactions with the card are done over NFC (ISO/IEC 14443 Type A), and
commands to/from the card may be in the usual ISO/IEC 7816-4 APDU format, or
“unencapsulated” bytes sent to/from the card are sent using a fixed instruction
set and return code structure – saving a few bytes per message. I’ve opted to
use their undocumented and proprietary format – I found it easier to work
with and with a maximum message of 60 bytes, the savings matter a lot.
While powered via NFC, the card maintains a small amount of state about the
connection between the reader and the card in its RAM, including if the
session is authenticated or unauthenticated. I’ll dig into how authentication
happens later, but it’s worth knowing that sessions can become authenticated
using one of the symmetric keys shared by the card and the reader. The vast
majority of the
DESFire commands
I know about tend to work while either authenticated or unauthenticated,
with a few exceptions (GetUid, ChangeKey, and ChangeKeySettings
for example).
In general, I found working with this card particularly pleasant. There is a
fair amount of backwards-compatible behavior and multiple methods of
communication that confuse things a bit, but overall, it was better than
average to integrate with. Kudos to the NXP team. If the docs on this chip
were public, things would be orders of magnitude easier – it’s not entirely
clear to my why they’re keeping so much of the interface documentation under
NDA, but it’s the largest knock against the chip, by far.
Authentication
I found a lot of really great resources outlining how the handshake and
protocol works for a DESFire EV3, especially from
Ridrix,
some public datasheetsThrRealRevK
and posts from AndroidCrypto.
The gist here is that, because the DESFire only does symmetric key operations,
the key exchange (a type of SKA – Symmetric Key Agreement) uses symmetric keys
to establish a unique session key which is used to sign or encrypt data
exchanged between the reader and the card. I’m not going to get too in-depth
here, since there’s a ton of other resources out there to dig into – but I
will do a quick high-level description to keep this post mostly self-contained.
The authentication protocol serves two main functions – to verify that both
parties know the same shared secret, as well as to act as a SKA to construct a
new session shared secret key. Here’s a quick overview of how a shared session
key is derived between the reader and the card using our symmetric keys
(AES-128 in the case below).
the reader requests to start authentication with the card (something like
AA 00 to start an AES Authentication handshake with keyslot 0x00).
The card will then reply with AF (a status code that indicates more data
is to follow), followed by 16 bytes (in the case of AES-128) of encrypted
(using CBC) data.
The hosts then decrypts this block with the symmetric key from keyslot 0,
returning the card’s session nonce.
The host generates 16 bytes (usually random) for its session
nonce.
The host sends an instruction of AF (indicating a continuation of
the previous command), followed by 32 bytes of encrypted data. When decrypted,
the first 16 bytes are our nonce generated in step #4, followed by the 16 bytes
provided by the card, decrypted in step #3, except where every byte is shifted
to the left by one place (the 0th byte is copied to the end).
The card will reply with 00 indicating a successful operation, followed
by 16 bytes, which when decrypted, is our session nonce from step #4, shifted
to the left by one byte in the same way that we did in step #5 with the card’s
nonce.
At this point, both the reader and card have confirmed the other party has
the same symmetric secret key. The session is now “authenticated” and a
“session key” is derived using the two nonce blocks. Two hashing keys (K1
and K2) are derived from this key, which is used to maintain an ongoing
CMAC hash of the messages
coming and going to/from the card.
From here on out, the session is “authenticated”, and responses from the card
which were previously “plain” will now contain a 8-byte CMAC signature,
which can be used to ensure that the replies in question come from the
active session.
In my implementation of the handshake
I opted to encode the handshake state into rust types, just so I wouldn’t make
any mistakes. The Handshake type contains the session internals (session
nonce values, keying state, to include IV, etc). This means the authentication
flow (from within my code) uses the Handshake struct to generate the commands
to send to the card in order:
/// Create a new `Handshake`, and return the
/// start auth command (something like `AA 00`)
fnHandshake::<Initial>::begin(
output: &mut [u8],
key: [u8; 16],
key_id: u8,
) -> (Self, &[u8]);
After we get a reply back from the card (the encrypted version of the card’s
session nonce, sometimes called Rnd_B in code I’ve seen), we transition
states from Initial into HalfOpen.
/// Given the card's encrypted response, generate
/// our session nonce and generate a reply
/// (something that starts with `AF` followed by
/// 32 bytes of encrypted data).
fnHandshake::<Initial>::rnd_b(
self,
output: &mut [u8],
input: &[u8]
) -> (Handshake::<HalfOpen>, &[u8]);
Now that we’re “HalfOpen”, we’re waiting to hear back from the card to ensure
that it, too, can byte-shift our provided nonce. Once we have the card’s reply,
we can check it using our complete helper, transitioning from HalfOpen to
Successful.
/// Check to ensure that the card replied with
/// our nonce byte-shifted by one place, indicating
/// that they know the symmetric secret in
/// this key slot.
fnHandshake::<HalfOpen>::complete(
self,
input: &[u8]
) -> Handshake::<Successful>;
Once the Handshake is successful, the only thing left to do is consume
the Handshake struct and turn it into the shared session key by running
it through the key derivation function.
/// Consume the `Handshake` struct and return the
/// new shared session secret key.
fnHandshake::<Successful>::into_key(self) -> [u8; 16];
From here on out we can use this session key for the remainder of our
interactions with the card – signing messages from (and sometimes to!) the
card, or encrypted messages to and from the card. This key is used in CBC block
mode, where the session IV is updated with the last block of the encrypted
data.
Unit Testing
A nice proprietary of the SKA scheme we’re using as part of DESFire is that the
derived session key is actually deterministic if you control your nonce RNG
(ok, actually, pretty true for most key agreements, but anyway), which means it
is possible to capture traffic over the NFC interface, and “replay” the NFC I/O
with cooked RNGs and ensure byte-identical messages and keys are generated.
Within desox-rs this is called replay (I’m creative), and I’ve got a few
replay sessions checked into VCS, which exercise a signficant amount fo the API
surface. All were derived from an actual session with a real DESFire card, and
can be updated with a live card and a --cfg flag.
Each replay file is a set of lines (request-response transactions), each
containing two space-delimited hex encoded NFC messages. For instance,
here’s an authentication handshake in replay format:
The code that generated that exchange came from the test stored adjacent
to that file – a handshake with the default DES key (all zeros), and
an RndA value hardcoded to 32c28fdafd3960de.
Since the card’s RndB is similarly unchanging (I’m replaying this file every
time), this will always derive the same session key, which means messages
(including encrypted ones or CMAC signed responses) will be identical, as well.
If you’re playing with the DESFire yourself, feel free to grab my replay
files if you need a
“known good” baseline.
By default this will run using the
MockBackend,
replaying each file – expecting a byte-identical request, and responding with
the harcoded customary reply. If the code (or test!) needs to change, updating
the tests is done by swapping the MockBackend out for a real one. Since I
had to do this a bunch during development, running cargo test
with RUSTFLAGS="--cfg desox_replay_rw" will, on run, overwrite the replay
file(s) for the executed test(s), ensuring all line-protocol changes are
explicitly caught and reviewed.
Observations
Most commands, even ones which require authentication, are transmitted without
CMAC signature(s) or encryption. CMAC signatures from the reader to the card
are not really used (except for writes to a file which specifies communication
must be CMAC signed), ditto for encryption (although that one is used for key
change operations, in addition to file writes on files that specify encrypted
communication must be used). The vast majority of commands take a “plain”
request from the reader, and return a CMAC signed response.
By my eye, this means that a malicious reader, or something otherwise capable
of holding the card online after communication with an authentic reader is
complete are able to execute privilaged commands (since one can simply ignore
the CMAC signatures on responses), so long as the command doesn’t require the
reader to provide CMAC signatures (or encryption), or allow the card to power
down.
Fun with DESFire
I’ve played around a bit with ways to use the DESFire cards in interesting
configurations, given what they’re capable of. Here’s some half-baked thoughts
I had while mucking around with the cards – these are all poorly thought out
sketches of some things we can do given the specific tradeoffs I see with the
DESFire card. It’s also worth noting that I don’t have any of the actual
documentation, and am not a cryptographic grown-up, so take these sketches with
a massive grain of salt.
The first thing that came to mind when implementing this is how the
authentication scheme can shift the boundary of what is and is not trusted
(assuming good secure keying, and provided the key slots and card/application
permissions are configured correctly). Rather than push the key material out to
the machine connected to the NFC reader (“reader machine”), I instead tried
turning the NFC reader and computer into something psuedo-untrusted by “merely”
having it pass messages from the card to a trusted remote system (“remote
machine”). This means that the “reader machine” is exchanging NFC data with the
card, but that data is being decrypted, encrypted and processed by the trusted
“remote machine” – the reader is unable to derive the session key.
For each of these, I wind up needing to authenticate – so there’s still a few
latent risks, but these can mostly be mitigated by asking for a readbacks of
any changed file(s), setting key permissions carefully, and requesting the
card’s UID via the encrypted channel – all of which would require the
symmetric secrets (which undermine the whole security model if comprimised).
This general construction is also subject to a hostile takeover of the
untrusted “reader machine”, since most commands (including destructive ones!)
are sent in “PLAIN” mode – the reader machine can wait until authentication
is complete and then inject commands into the card and “simply” ignore the CMAC
signatures on responses, severing ties with the remote machine. As such, we
also need to take steps to ensure that the key being used is not one that
allows any access beyond what is allowed. Here were some ideas I sketched out
off the back of this theory.
The “second-factor”
Given some established (and authenticated) connection, part of the initial
authentication flow may use the DESFire card to prove physical control over it
as part of a handshake. This can serve as a second factor during some
authentication flow, requiring physical card presence at a reader to fully
initialize a connection. This does have one glaring downside, however – it’s
phishable. To use this “for real”, we’d need to take some steps to prevent
obvious MITM flows (XOR the NFC messages with the URI as seen by the client?),
but maybe there’s something interesting there.
This also has a second interesting attribute – when used as part of a physical
system authentication flow, this becomes a logical place to inject access
control, being able to determine if some person is permitted to operate some
device at that particular time (Is “Joe” current on his Laser Cutter
certifications?) I think of the ideas I landed on, while conceptually
interesting (using an employee id card as a 2FA token, it’s very fast), this
one is the least likely to turn into something real.
The “encrypted-cookie”
This construction, when paired with an encrypted DESFire file, allows the
“remote machine” to read/write an ’encrypted cookie’ to the card – storing
small amount of encrypted data that the “remote machine” can read/write, but
not the “reader machine”, since this uses an encrypted and authenticated
channel from the “remote machine” directly to the DESFire card, without any
intermediate hosts needing to be fully trusted. I keep calling this the
“encrypted cookie” in my head because it feels conceptually similar to how Ruby
on Rails and Laravel handles cookies.
We’d need to take a few extra steps here (for instance, ensure that you read
the cookie back over the encrypted channel after writing to prevent a malicious
reader from dropping writes) to secure the system, but it feels like the
structure of this is definitely decent.
The “takeover”
This time, let’s say the computer attached to the NFC reader (“reader machine”)
is semi-trusted. For this scheme, our trusted “remote machine” and the
“reader machine” pass messages over the network to handle authentication to the
card (as above), where the handshake data is being decrypted, encrypted and
processed by the trusted “remote machine” as usual. However, once the
authentication handshake is complete and a session key has been derived, the
“remote system” return the session key to the “reader machine”, giving it a
one-time-use key and authenticated session to the card.
We need to be careful about global/application permissions and key access
control to files – but in this construction, we can allow the “reader machine”
to take over privileged actions using a scope-limited DESFire key without
handing over the card’s true keying material (preventing cloning of the card).
This can be helpful to ensure messages to/from the card are truely from the
card (verifying CMAC signatures), enables the “reader machine” to directly
read/write to/from encrypted file(s), but allows the symmetric key material to
remain in as few places as possible – which is critical given compromising
that secret will undermine the security of the entire system.
Starting with strongSwan 6.1.0-1 (currently
in Debian unstable and targeted at Debian 14 Forky), the IKEv1
protocol has been disabled. This is aligned with
upstream decision. Considering IKEv2 is already nearly old
enough to drink in the USA (RFC 4306 will turn
21 next December) and IKEv1 has weaknesses, the disabling is
long overdue amd will permit upstream to remove some code in the
upcoming years.
At this point there is no good reason not to migrate to IKEv2
and exposing IKEv1 code in all Debian installation is no longer
relevant. All IKEv1 users using Debian 13 Trixie (either site to
site, gateway or roadwarrior client) should investigate IKEv2
protocol (or other options).
Note that some plugins have also been disabled upstream for
security/maintenance reasons and we followed suite in Debian. The
Debian relevant ones are: af-alg, led, padlock.
Replacing lxc with Incus for Debian CI, by Antonio Terceiro
At the beginning of this month,
the default backend for tests was changed to incus-lxc,
leaving only a few dozen packages explicitly configured to run as lxc. Those
packages got bug reports filed
and once those bugs are fixed they will be migrated over to incus-lxc. This
marks an important milestone for Debian CI, as the low level of isolation
between the worker host OS and the OS under test when using lxc was a
significant source of instability in our platform. Common causes for test
failures when comparing runs under plain lxc with runs with incus-lxc are
documented in the wiki page
linked from the bug reports.
security-tracker git performance, by Helmut Grohne
Developers working with Debian’s security-tracker
have reporteddegrading
performance for a while. The options for solving this are few with repository
sizes now reaching 30GB for a repository whose working tree is a mere 60MB.
While there have been a number of proposals for changing the storage layout,
Helmut evaluated options not requiring such changes. Much of the problem hinges
on the 50MB data/CVE/list file that is updated in most commits. Efficiency
hinges on representing its content in git packs.
Practically, git frequently fails to represent its content as a delta and stores
a full copy that is typically compressed to 12MB. Add a few thousand 12MB full
blobs and you quickly have a large repository. In particular, the copy at
salsa.debian.org stores many such full blobs, so pulling from it consumes
significant bandwidth.
One might think that running git gc helps, but its utility is limited here.
When git stores objects in packs, it stores the
history in reverse.
It starts with the current version and represents older objects as differences
(deltas) to more recent objects. A delta effectively is a sequence of copying
ranges from a base object and insertion of new content. Given that humans tend
to add content over time, those additions are often represented as deletions.
The crux here is finding that base object. Given that the data/CVE/list file
is large, finding it involves comparing quite a few versions of it with each
other. This requires both a significant amount of RAM and CPU time. How many
objects git considers for this comparison is controlled with the --window
option. It is beneficial if the base object does not come from a direct child
commit but skips over commits. In doing so, long chains of deltas are avoided.
The --depth option controls the maximum chain length.
In this search, git combines all objects into a single window to facilitate file
renames. As such, it tends to compare data/CVE/list with data/DSA/list,
data/DLA/list and others. This is less than helpful and tends to evict all
data/CVE/list versions from the comparison window such that a new full blob of
it becomes required. Since the security-tracker repository rarely renames files,
we can ask git to instead consider one window per filename
via --path-walk. Once doing so, it becomes quite a bit better at finding
suitable deltas. The technique is less applicable to older history (2025 and
earlier), but given a lot of RAM and a bit of partitioning, git gc can shrink
that as well. Combining these techniques, we can shrink the repository into
700MB and keep new growth somewhat under control.
Temporarily, Helmut is providing a proof-of-concept mirror at
git://git.subdivi.de/~helmut/security-tracker.git using these techniques.
Avoid pulling from it directly as it does not provide a secure transport. While
it does reduce the amount of data being downloaded, it does not resolve a number
of related problems. After downloading, a git client will still expend
considerable amounts of CPU on verifying received deltas and attempting to
git blame data/CVE/list is not improved in any way. Still, there is work on
integrating some of the improvements back into salsa.
OpenSSH GSS-API split, by Colin Watson
In an option review
Colin did in 2024, shortly after the xz-utils backdoor,
he explained that having GSS-API authentication and key exchange support in the
main OpenSSH packages is problematic. The key exchange patch is large and
intrusive. Even linking to the necessary libraries isn’t without risk: as the
Ebury malware attack demonstrated
way back in 2009, each extra library linked into security-critical daemons such
as sshd (or nowadays into its privilege-separated helper programs) can modify
the behaviour of the daemon even if you aren’t doing anything that would involve
calling into that library. Of course some of that risk remains, but as
Damien Miller wrote,
minimizing the number of libraries that end up in the address space of sshd
and friends is still valuable.
This split is now complete in testing. As of openssh 1:10.4p1-5, the OpenSSH
client and server are built without GSS-API authentication and key exchange
support. If you need those features, install openssh-client-gssapi or
openssh-server-gssapi instead, as appropriate. Debian 13 (trixie) already has
packages with those names that just depend on the regular openssh-client and
openssh-server so that you can pre-emptively install them, as
documented in the release notes.
The new openssh-*-gssapi packages have relatively tight dependencies on
openssh-common, in order for the testing migration system to ensure that we
can’t forget to keep them up to date. This will mean a bit more ongoing work on
each new upstream version, but it should be manageable.
Catching up on DebConf video publication, by Stefano Rivera
DebConf 25 videos suffered terribly
from a bug in voctomix that
stopped us from being able to publish the videos immediately after the
conference. Ivo De Decker did some work on the videos earlier this year, fixing
enough of the audio problems to make the videos at least intelligible.
While at MiniDebConf Winterthur Stefano
published all the videos from 2025 and 2026 to PeerTube
and YouTube.
DebConf 25 and DebConf 26 videos as well as all the videos from miniconfs that
had got caught up in the backlog were published.
Miscellaneous contributions
Carles used po-debconf-manager
to review and submit Catalan translations for six packages.
Carles prepared a prototype
of building webwml and running “tidy” (linter) in Salsa.
Carles created different webwml
merge requests
with different fixes: invalid links to different architectures, 404s to external
sites, fixed templates with invalid URL to favicon.ico, making USE_SAMPLE_FILES=1
to avoid fetching external files, typos in English, etc.
Antonio did several Salsa maintenance tasks,
including his first platform security update and investigating an issue that was
causing the background tasks to be severely delayed, imparting the site usability.
Thorsten tried to fix some bugs in hplip and cups. Unfortunately this did not
result in an upload this month, but stay tuned!
Helmut continued to report undeclared file conflicts and correspond about them.
Helmut reviewed and merged Johannes Schauer Marin Rodrigues’ work on adding
hurd support to debvm.
Helmut fixed a pile of minor issues in the support libraries underpinning
unschroot.
Helmut adapted rebootstrap to archive regressions and improved its handling of
non-default gcc versions.
Santiago continued helping Aryan Karamtoth with the GSoC project on
introducing livepatching support for linux. Several challenges have been
identified in the recent couple of months, and Santiago is trying to guide Aryan
to address them and design the solution. The mentoring is done in collaboration
with Emmanuel Arias.
Lucas onboarded a co-maintainer for Valkey.
Lucas has started to work on a draft for the Debian Outreach project for
onboarding newcomers interested in Debian packaging via a video course.
Stefano worked on the after-math of DebConf 26, handling the bursary expense
reimbursement process and winding down the conference finances.
Stefano uploaded Python 3.15 RC 1 to Debian unstable.
Stefano did some budgeting and travel planning for the DebConf 27 site-visit
in Asahikawa, Japan. The DebConf committee sends a couple of members to meet
with the local team and view the venue together, in advance of the next DebConf.
The Reproducible Builds project relies on several projects, supporters and sponsors for financial support, but they are also valued as ambassadors who spread the word about our project and the work that we do.
Today, however, we will be talking with Jochen Sprickerhof,
one of the newer members of the Reproducible Builds project core team.
Vagrant Cascadian: Could you tell me a bit about yourself? What sort of things do you work on?
Jochen Sprickerhof:
I am a freelance programmer working on Open Source. Mainly doing Debian, F-Droid and some smaller software projects. In general I made it a habit to look into every software I use and try to fix bugs or add features I need. In Debian, I maintain about 180 packages with topics covering home banking, build systems and robotics. Most of my time, I currently work on reproduce.debian.net, where we try to bit-for-bit reproduce the packages distributed by Debian.
Vagrant: Could you describe the path that lead you to working on reproducible builds?
Jochen:
I started my Debian journey as a teenager, converting my school to Debian and serving as its system administrator for 13 years. After studying Applied System Science, I joined the university’s robotics labs, where I worked on the Robot Operating System (ROS) and the Point Cloud Library (PCL). In the end, I enjoyed programming more than writing papers, so I eventually left academia for a robotics startup. Some years ago, I realized that the open source work I was doing in my spare time was actually the work I cared most about. Nowadays I am really grateful that I can spend my days working on things I find important and have lots of fun with.
Vagrant: What projects did you recently make big progress on?
Jochen:
A recent example is metasnap.debian.net. It is a ‘meta archive’ of
snapshot.debian.org which is itself archive of all packages in Debian. But let
me explain it the other way round: with reproduce.debian.net, we try to reproduce the packages as they are distributed by the Debian archive. For that, we need the same build environment (compilers, libraries, build tools, etc) that was used by Debian back when the original package was compiled. Luckily, snapshot.debian.org has all those packages, but they are not easily accessible via apt, Debian’s package manager. So, metasnap provides a mapping from a package name and version pair to the APT repo on snapshot.debian.org needed to download it from. It was created by josch some time ago, and it’s awesome work. But when we tried to reproduce more and more packages on reproduce.debian.net, we found that some were missing packages from the build environment — even though they where visible on snapshot.debian.org. We found that metasnap excluded some archive areas because they where not expected to be needed. Reimporting all the data took more than two months and surfaced a couple more flaws.
With this fixed, we were able to build more packages, only to find out that metasnap also needs better support for version numbers. Luckily we were able to rewrite the data in a day instead of starting the import again.
Vagrant: You have been working on infrastructure to support reproducible builds for a while. Has recent adoption of reproduce.debian.net into the Debian release tooling changed the focus of your work?
Jochen:
Quite a bit. When we started reproduce.debian.net in 2024, only around 33% of the packages could be reproduced successfully. Today we are above 98%. Most were not bugs in the packages themselves but in the infrastructure. Similar to the metasnap issue I reference above, packages just needed a rebuild because something else, like the toolchain, was fixed in the meantime. In May, people from the Debian release team and the Reproducible Builds project sat together and decided that the overall state is good enough, and now packages that regress on reproducibility are blocked from entering the next Debian release. But that does not mean all the work is on the shoulders of Debian package maintainers. Since then I have been constantly looking at the migration tooling to spot regressions and provide fixes. Furthermore, a couple of maintainers reached out to us for help and I hope more will do so in future.
Vagrant: What is one small thing you (or others) have not yet gotten to that you would really like to see?
Jochen:
The central tool to reproduce Debian packages is debrebuild, also written by josch. Currently it has two ways to retrieve the build dependencies of a package. Either it uses metasnap.debian.net (as explained above), or it can access the Debian unstable APT repository directly. This allows to test packages locally before everything is indexed on metasnap by compiling against Debian unstable. But actually there are many other APT repositories to query, like Debian stable or even derivatives. Adding support for an optional list of APT repositories in debrebuild would be great. That would also be a big step to support reproducing other Debian based distributions.
Vagrant: … and one big thing?
Jochen:
It would be great to integrate metasnap.debian.net into snapshot.debian.org. There is some discussion on it already in Debian bug #650783.
Vagrant: What are the tools you use the most?
Jochen:
According to my fish shell history:
$ history | cut -d' ' -f1 | sort | uniq -c | sort -nr | head -10
36199 git
20941 vi
12271 rm
8599 cd
7917 ls
6407 apt
5631 grep
4249 mv
3655 dpkg
2873 cp
Vagrant: So, is the fish shell reproducible? I remember it did not used to be…
Vagrant: Oh, that’s a nice URL to check for reproducible package… you can just pass the source package name to check the current results?
Jochen:
Yes. Another one is udd.debian.org/reproducibility/, where you
can list all packages of a Debian maintainer. It also lists source reproducibility and has nice filters as well.
Vagrant: What tools do you use specifically working on reproducible builds?
Jochen:
I don’t have statistics for that, but I would say sbuild to build the package, debrebuild to reproduce it, and diffoscope to analyze the differences. Obviously I also need run apt source <package> or use git-buildpackage to get the sources and all the tools I mentioned above.
Vagrant: So how many packages are left to build reproducibly, and once those are finished, what is next?
Jochen:
Right now, reproduce.debian.net shows over 98% reproducibility, though there are still over 650 package left and some will probably need a lot of work. But actually I think making packages reproducible is just the first step. For me, this is a project to build confidence in the system. To reproduce a package we have two parts: the source of the package and the build environment. Fixing the packages means gaining confidence in the first part but we still rely on the individual build environments for each package as we need to use the same compiler that was used when the package was build initially. Because of this, we have to keep around every historical version of all toolchain packages. I really would like to remove this extra archive, which means we would have to rebuild all of Debian around release time. I am dreaming of a Debian release where you could bit-for-bit reproduce every package just from the released versions. Due to how Debian works, however, this is not a trivial rebuild and it would need some work on the infrastructure. By the way, initially there was a third component to pay attention to: any connection to the outside world during the build. Luckily we fixed the Debian build daemons to not allow network connections during the build some time ago.
Vagrant: Thanks for all that work, and taking the time to tell us a bit about yourself!
Jochen:
Thanks a lot for the interview!
For more information about the Reproducible Builds project, please see our website at
reproducible-builds.org. If you are interested in
ensuring the ongoing security of the software that underpins our civilisation
and wish to sponsor the Reproducible Builds project, please reach out to the
project by emailing
contact@reproducible-builds.org.
I asked the banker who is my reference at the bank something like this:
Give that we are talking about the consequences of the tantrum of a fascist
foreign government, what happened to them (who are also people close and dear
to me), in some future can very well happen to me.
Suddenly my risk profile shot up under the roof.
What do you suggest me to do? Should I find a trusted source of gold bullions
to bury under the cellar at home?
The answer was something like this:
Sadly YES, given that the USA have a sort of financial monopoly they can
entitle themselves to arbitrarily define a person/organization as a terrorist
without any trial or judicial course, and as a consequence apply sanctions
that cannot be effectively counteracted, not even abroad.
I didn't have this in my 2026 bingo card, but here we are.
For more details, see:
the A/I press release under "12. Banca Etica: the sanction also affected an Italian bank account"
After having witnessed how easy it is for good people to lose a .org domain
over a fascist tantrum (you can follow the Autistici/Inventati story
here and
here), I've started moving all my infrastructure to
differently managed TLDs.
enricozini.org and enricozini.com will keep being functional for the time
being, as dropping a domain makes it available for squatting and impersonation.
These new domains are now online, with working web and emails:
A new version 0.0.7 of the td package for accessing
the twelvedata API for
financial is now on CRAN, and
has been built for r2u.
This release combines the standard set of maintenance changes that
accrue in a four and a half year period (!!) as that much time has past
since the previous release. But it also contains two contributed
functions to retried, respectively, a profile (available under a paid
plan) and a set of main fundamental data statistics, both provided
Kenneth Rose.
The NEWS entry follows.
Changes in version 0.0.7
(2026-09-13)
Added fun_statistics and fun_profile
function
Expanded README.md with additional badges, and updated
URLs
The third release (in twelve years !!) of the sanitizers package
is now on CRAN. sanitizers
provides ‘true positives’ for programming errors detected by the Address
Sanitizer and friends such as the Undefined Behavior Sanitizer. This
permits validation of the setup when chasing such bug reports: it allows
us to ascertain that the compiler (and instrumented R version) are
correctly set up and the errors we expect to be reported are in fact
reported. That established, a proposed fix no longer exhibiting that
same error will then likely be a suitable one.
A little over twelve years since the first release, and three years
since the second one, this update brings chiefly internal package
changes and maintenance. No functional changes, no behavioural
changes.
The brief NEWS entry follows.
Changes in version 0.1.2
(2026-09-12)
Expanded README.md with additional badges, and updated
URLs
Updated continuous integration multiple times
Switched to Authors@R
Added usage, arguments and
value sections to manual page
In our reports, we try to outline the most important things that we have been up to over the past month. As a quick recap about what problem our project intends to solve, whilst anyone may inspect the source code of free software for malicious flaws, almost all software is distributed to end users as pre-compiled binaries. The motivation behind the reproducible builds effort is to ensure no flaws have been introduced during this compilation process by promising identical results are always generated from a given source, thus allowing multiple third-parties to come to a consensus on whether a build was compromised or not.
CISA, the Cybersecurity and Infrastructure Security Agency of the U.S. government published some the joint guidance entitled Minimum Elements for a Software Bill of Materials (SBOM), which updates and supersedes the baseline 2021 version covered in previous editions of these reports.
Whilst the PDF is worth skimming, the interesting changes include that the specification now mandates standard cryptographic hashes: unlike earlier standards that allowed hash omission or manifest-only parsing, hashes must be computed from the output. This is is important for reproducible builds, as it ensures the recording of the metadata required to demonstrate the shipped software matches the build output precisely where applicable. In addition, where the top-level only dependency limitation that was present in the 2021 version has been removed in favour of complete coverage with no minimum depth. That is, SBOMs are expected to reflect all linked libraries, vendored dependencies and other build-time inclusions.
The basic idea behind bootstrappable builds is to create a system that can be built without relying on pre-built artifacts. “Can we go from zero to the modern day without having to just assume the existence of these already-built-for-us artifacts?” The classic recipe for yogurt requires some yogurt to start the process, which is like how we normally build a C compiler today—we start with an existing C compiler binary. You might think about making sourdough bread with your grandmother’s starter brought over from the old country; “we’re basically making C compilers with Dennis Ritchie’s starter carried over from Bell Labs”.
The reason I like the idea of making reproducible builds work is that I think it can be done in such a way as to not require any work on the part of the producer of a distribution (which is a technical term for sdists or wheels, i.e., the people who upload stuff to PyPI), and thus make reproducible builds very low-friction for people to opt into supporting. […]
Brett goes on to outline “What’s missing from the specs” and how reproducibility might be visible on PyPI to consumers:
Assuming all of this comes to pass and we record the where the source code is that went into a distribution and the software used to make the distribution, how do we make it useful to people? Does every person who cares about having a secure supply chain have to rebuild everything they use themselves? Is there some way for even people who don’t care about this stuff to benefit? […]
Distribution work
In Debian this month, 23 reviews of Debian packages were added, 28 were updated and 27 were removed this month adding to our knowledge about identified issues. A number of issue types have been updated as well, such as the addition of a new toolchain issue related to python-traitlets […], and the note for an existing issue related to texi2html was updated as well […].
Lastly, Bernhard M. Wiedemann posted another openSUSEmonthly update for their reproducibility work there.
Unreproducible builds under EROFS filesystem fixed
diffoscope is our in-depth and content-aware diff utility that can locate and diagnose reproducibility issues. This month, Chris Lamb made a number of changes, including preparing and uploading versions 327, 328 and 329 to Debian. In particular, he ensured that diffoscope did not require python3-guestfs in the autopkgtests on 32-bit architectures in order to fix Debian bug (#1144372) […]. Colin Watson made an additional change, handling a potentially missing openssh-client package when running the autopkgtests […], and Jochen Sprickerhof made a similar change to cope with missing cpio and qemu-img functionality […] whilst also updating the XML comparator to be considered when comparing SVG images […].
Yet again, there were a number of improvements made to our website this month as well. For example:
Lastly, a large number of commits were pushed comprising an interview with Reproducible Builds developer Jochen Sprickerhof to be published within the next week. […][…][…][…][…][…][…]
Six new scholarly papers
Jens Dietrich, Spencer Sun, Tim W. White and Behnaz Hassanshahi (the result of a collaboration between Victoria University of Wellington and Oracle Australia published a paper this month entitled No Snake Oil: Verifying Python Package Builds. Drawing on the metaphor of “snake oil”, that is, a fake or ineffective medicine or solution sold with exaggerated claims of curing or fixing everything, the authors write that
Two tools that are designed to automate [PyPI] rebuilds and run them at scale are macaron and oss-rebuild. We study 12,180 popular releases from PyPI and find that the byte-for-byte equivalence rate is generally low. We analyse the reasons why they produce different wheels, and find that equivalence between the original and rebuilt wheels can often still be established, preserving most of the guarantees users expect from rebuildable releases. We present and evaluate daleq4py, a tool to establish the equivalence of Python wheels through the kernel of a normalisation function that is based on provenance-preserving datalog rules. Experimental results show that daleq4py substantially expands the set of rebuilds that can be accepted as equivalent.
Dimitri Kokkonis, Michaël Marcozzi and Stefano Zacchiroli published an article this month titled Not In My Git Yard: Catching Backdoors at Commit and Release Time on the topic of “code-level backdoors” — that is, “stealthy code changes that grant hidden privileges via secret triggers”. These issues:
… pose a persistent threat to opensource software. Known attempts to inject such backdoors into widely used projects through malicious commits, tampered release packages, or compromised third-party dependencies, were stopped only by luck and manual review. Existing Continuous Integration (CI) pipelines cannot detect these attacks, and downstream binary analysis tools require substantial manual effort. In this work, we present Lily, an automated approach that strengthens open-source development and release processes against backdoor injection. Lily integrates a backdoor detection mechanism into (1) CI pipelines to block malicious commits, and (2) release vetting workflows to prevent tampered releases or compromised dependencies from entering large ecosystems, such as Linux distributions.
Ranindya Paramitha and Laurie Williams of North Carolina State University along with Christian Kästner of Carnegie Mellon University published a paper this month with the title of The Software Supply Chain as a Market for Lemons: A Multivocal Review of Trust Signal Collapse. (A “lemon” in American English, is a vehicle that “turns out to have several manufacturing defects”.) Their abstract is as follows:
Practitioners evaluating open-source dependencies rely on cheap trust signals, e.g., stars, download counts, and contributor activity, as substitutes for direct code inspection, assuming those signals reflect genuine trustworthiness. Prior work has documented individual signal gaming, but the landscape of collapses across all dependency-adoption signals, as well as the ecosystem’s response, remains unexplored. The goal of this study is to aid software practitioners in understanding the reliability of dependency adoption trust signals, such as download counts and contributor activity, by conducting a multivocal review of 252 Google Search sources and 870 Reddit threads.
Worryingly, after their review, the authors conclude that “cheap trust signals collapse under three simultaneous forces: adversarial manipulation, gaming techniques indistinguishable from legitimate behavior, and non-adversarial AI-driven inflation.”
Ken Thompson’s trusting-trust attack, in which a compromised compiler backdoors the programs it builds and reproduces the backdoor in subsequent rebuilds of itself, is widely regarded as a threat specific to compilers. We show that it is not. We construct a complete trusting-trust attack around GNU strip, an ordinary build utility that neither inspects nor generates source code, using only manipulations of finished ELF files.
Scarily, in the authors’ example, “a single tampered strip in the binary seed implants a payload that propagates from one generation of strip to the next and survives into the final standard environment after the seed leaves the dependency closure […] without failures and backdoors”.
Mehdi Keshanimm, Amirhossein Rahmati, Mohammad Hossein Aref and Abbas Heydarnoori published a paper that is currently under review at Emperical Software Engineering titled AROMA+: A Study of Factors Affecting Reproducible Builds in the Maven Ecosystem. (Maven is a/the build automation tool used for Java projects.) In their paper, the authors note that
[…] reusing external software in a project presents a security risk when the source of the component is unknown or the consistency of a component cannot be verified. The SolarWinds attack serves as a popular example in which the injection of malicious code into a library affected thousands of customers and caused a loss of billions of dollars. […] Our research aims to support [reproducibility] efforts in the Maven ecosystem through automation. We investigate the feasibility of automatically finding the source code of a library from its Maven release and recovering information about the original release environment. Our tool, AROMA+, can obtain this critical information from the artifact and the source repository through several heuristics and we use the results for reproduction attempts of packages on Maven Central.
[A]rtifact verification requires more than deterministic builds: a verifier must also recover the source state, build environment, dependencies, and build instructions that produced the artifact. Decentralized-build ecosystems make this difficult because artifacts are produced through heterogeneous tools, maintainer-controlled workflows, and fragmented metadata. As a result, it remains unclear how often artifacts in these ecosystems can be independently verified. This paper studies artifact verifiability across four popular decentralized-build package ecosystems. We define an independent verifier model that relies only on registry-derivable metadata and an artifact comparison model with tiered equivalence levels. We implement these models in an Artifact Verification Pipeline and use it to measure artifact verifiability across the target ecosystems.
The authors conclude that “beyond build determinism, verifiability is limited by missing source and build metadata, implicit release transformations, and unconventional build practices”.
The Reproducible Builds project detects, dissects and attempts to fix as many currently-unreproducible packages as possible. We endeavour to send all of our patches upstream where applicable or possible. This month, we wrote a large number of such patches, including:
Two weeks ago, I released AppStream 1.2.0. This release contains a lot of great changes, but one of the most important ones concerns how media are being handled, and AppStream’s default image export format.
AppStream is a Freedesktop metadata standard to describe software components. That can be anything from system services over fonts to console and graphical applications. AppStream metadata is supposed to give users enough information to decide whether they want to install a piece of software, to represent that piece of software, and to give the operating system enough information to decide whether a software component should be installed automatically and (to some extent) what capabilities and relations it has, to provide the user with sensible options.
Especially for the first two goals, and especially for GUI applications, AppStream supports icons and screenshots, which are used to showcase applications. Today, AppStream is used by all kinds of services, from Linux distributions over firmware updates to Flatpak and desktops directly. AppStream’s original design however comes from the perspective of Linux distributions in 2011, where you may want to browse the software catalog offline, without delay, and without pinging an external server (which could be a privacy concern).
Therefore, a common way to deploy an AppStream-enabled software repository is to ship all icons of all applications in the repository to the user as part of the repository metadata download. AppStream does support remote icon downloads nowadays, and for a while I thought that this would become the default eventually. However, especially in today’s world, having a bandwidth-saving, instantly responsive, privacy-protecting application browsing experience seems more important that ever.
PNG images are great!
The only format that AppStream supports for icons and screenshots (which are downloaded on-demand from your distributor’s CDN) has always been exclusively PNG. PNG images are perfect for icons, because they compress well (especially for common icon shapes), are fast and simple to load, and can be loaded anywhere, by any toolkit or webbrowser. They also ensure we deliver faithful screenshot images, even though we may have scaled or re-rendered them. Still though, PNG images are less great for screenshots, as they are not very efficient, which puts strain on any CDN that has to deliver them, as well as on people’s internet connections when browsing screenshots. Having smaller thumbnails alleviates that problem a little, but does not fully solve it.
But even for icons, PNG could be improved upon: In many cases, icons are re-downloaded with the repository metadata again and again, so having a large icon tarball adds up to the data transferred during metadata refreshes. AppStream also now supports large 128x128px icons, which nobody in 2012 expected we would need, adding even more data that will be re-downloaded. Saving some space here translates directly to lower bandwidth costs as well as faster downloads for users.
To improve PNG file sizes, the AppStream Compose library, which handles all image processing and metadata catalog composition, was running optipng on all generated PNG images. That does create smaller PNG images, but they were still relatively large compared to other image formats.
For a long time though, there was no alternative to PNG images for icons: There was no lossless image compression format that could give us the same quality as PNG images and that was also widely supported.
JPEG-XL vs PNG in AppStream
Since 2021 we have JPEG-XL (JXL), which offers a true lossless mode with often better compression than PNG. The issue was that JPEG-XL wasn’t widely supported. Then, in 2025, the PDF Association selected JPEG-XL as the preferred image format for HDR images in PDFs, and now we are finally getting browser support and more ubiquitous availability of the format (you can try it right now in Firefox!).
For screenshots, using JXL’s lossy mode, it has obvious and extreme size advantages over PNG, so supporting JXL or WebP for screenshot images was an obvious choice. If JXL would support the lossless case very well as well though, we could serve many use cases with the same exported image format, which is very attractive to me.
So, the obvious next question was whether it was worth the pain of switching the icon format, so I did some measurements on real icons. For that I used the AppStream component icon pool that Debian Unstable ships, which is almost 5000 application icons of various sizes, and converted them to PNG:
Icon size
Icons
PNG total
JXL total
Pool saved
PNG avg
JXL avg
Median saved
Mean saved
Worst
Best
Larger as JXL
48×48
1544
3.7 MiB
3.0 MiB
17.8%
2.4 KiB
2.0 KiB
17.9%
16.7%
-118.7%
60.0%
206
64×64
2018
7.0 MiB
5.8 MiB
17.8%
3.6 KiB
2.9 KiB
18.0%
15.8%
-112.7%
70.0%
279
128×128
1411
11.2 MiB
8.7 MiB
22.0%
8.1 KiB
6.3 KiB
20.1%
17.5%
-89.7%
61.0%
209
TOTAL
4973
21.9 MiB
17.5 MiB
19.9%
4.5 KiB
3.6 KiB
18.6%
16.6%
-118.7%
70.0%
694
PNG images saved with libpng at effort=4, compression=9, then optimized using optipng -o2, JXL images encoded using vips jxlsave lossless=1 effort=7 strip=1 via VIPS/libjxl.
As the table shows, using lossless JXL images over size-optimized PNG images (using optipng’s default settings) provides a roughly 20% gain. This does not look like much, until you consider how often these files are downloaded: A 20% file size reduction may only save 1-2 MiB of disk space, but if they are downloaded over and over again by many clients, it will save a lot of bandwidth.
Interesting JXL encoding findings
As a sidequest, I was curious why some images were larger than their PNG counterparts when encoded with JXL, and what the ones that were significantly smaller were.
In short, the biggest size reductions for JXL existed on images that were already small as PNG, and contained large, flat color surfaces with hard edges and simple shapes. They were not very interesting, and much of JXL’s wins come from accumulating smaller gains across all files, which compound the bigger icons get (especially at 128x128px, where JXL truly shines).
The events were JXL loses to PNG are more interesting: For example, it does quite poorly with pixel-art images that have a lot of repeating patterns. Those are encoded well by PNG, but less efficiently by JXL. Take for example Vonsh:
Icon of Vonsh, an SDL-based snake game, which PNG compresses better than JXL
My guess is that while PNG can exploit the repeating pixel patterns for compression, JXL’s predicts surrounding pixels from its neighbours, which fails too often and makes it pay almost full entropy per pixel. In this single rare case, the PNG is at 5.4 KiB, while the JXL is almost 8 KiB in size.
Other cases I looked at were arguably buggy input data, where color channels were hidden under the alpha channel of the input image. PNG could probably again exploit repeats, while we were forcing JXL to encode pixels that were invisible in the final image. This is arguably a problem with the original input data. Currently, AppStream does not make any changes to icons at all, but in future we might add a filter that removes invisible colors from images to solve this pathological case (it was only two icons out of 5000 though, so it is not a high priority).
The third case I found where JXL loses to PNG were icons with checkerboard-like patterns:
For those, PNG can likely again exploit the repeating patterns, while a checkerboard layout is pretty bad for left/top predictors like JXL’s. However, in this case the size difference (and loss for JXL) is only 450 bytes, so even though JXL loses to PNG, it does so not by much.
JXL in AppStream
Given these findings, JPEG-XL is the default image format starting with AppStream 1.2.0. AppStream Compose will encode all images losslessly as JXL, while screenshots are encoded in lossy mode at Q=90 effort=7. Since the optipng step does not happen for JXL images, this comes at no speed penalty and is even a bit faster on modern x86_64 CPUs (where libjxl can use SIMD). PNG is still available, and Compose can be told to switch between the two formats.
Upsides of JXL in AppStream right now
If you use JXL in Compose or the recent release of appstream-generator, you will get much smaller images and, for screenshots, will benefit from other JPEG-XL features such as progressive decoding, providing a far nicer user experience. libAppStream has supported JXL icons since version 1.1.3, so your clients will need that version or a newer one, and all software centers will have to support loading JXL images (which all of them do, provided the right plugins are installed).
Downsides of switching to JXL too quickly
JXL is a very new format, so web browsers might not yet display it if you are serving webpages. Your clients may also have bugs in processing JXL images, as the format is still “new”. For example, switching on JXL in Debian sent KDE Discover into an infinite loop on startup while trying to load the icons (an issue which has been fixed, but clients will need that patch first before JXL is switched on).
This currently makes JXL enablement only possible when you know that your clients can support it. This is the case for me in Debian Unstable and Debian 14, which are using JXL images for a few weeks now, but not for any older releases. Platforms like Flatpak have it even harder, because they do know even less about their clients. So, even though it has big advantages, you may want to hold off on using JXL right away, and force PNG by setting the ImageFormat key to png in appstream-generator‘s configuration, or passing --image-format=png to appstreamcli compose.
It is also worth mentioning that JPEG-XL is much, much slower on systems that do not have SIMD instructions or for which the libjxl/jxl-rs library does not have them (such as apparently riscv64 right now). If this is a concern, you might not want to switch to JXL right away.
Media pipeline improvements
Besides the JXL default change, AppStream 1.2.0 also comes with a complete overhaul of its media processing pipeline. While libappstream, AppStream’s main library, does not do any media processing and comes with very minimal dependencies to be embedded in client applications and used on servers, the same can not be said about libappstream-compose, AppStream’s library to build metadata generating applications (the server-side part, usually).
The compose library has to render fonts into font specimen cards, inspect translation files, render SVG images, decode all kinds of raster images, inspect video files, etc. Especially the fonts, and the fact that fonts can appear in SVG images, has caused issues in the past, as libappstream-compose is a heavily threaded library and most font libraries can only work from a single thread. This forced the library to essentially go into single-thread mode anytime anything that could touch a font was being processed.
AppStream also originally was created for a “safe world” where applications were vetted by the distributors before their metadata was processed. This is increasingly not the case, so it made sense to put at least a few guardrails on the most complex part of the pipeline: The media processing. As part of the change, media processing was split out into a separate worker process. This solved two problems at once: Font handling was isolated in a single-threaded binary – if we wanted to handle fonts in parallel, we could simply spawn more workers. And, being in a separate process, the media processing could now be sandboxed.
As part of the multiprocess changes, Compose also switched from using GdkPixbuf to VIPS for image processing. The latter allows for much more fine-grained control over the image output and encoding, and comes with a lot of well-maintained filters and operations, which made it possible to eliminate a fair chunk of AppStream’s hand-rolled image processing operations. As part of this transition, we unfortunately lost the ability to read XPM images, which dropped about 20-30 applications from the pool at Debian. But in the name of security, this is a sensible choice, especially since most XPM icons were very small and low-resolution, and applications using them could benefit from adding a high-quality PNG icon anyway. With VIPS, we also now restrict the amount of image formats we can load to a sensible set, so extremely niche or unexpected formats will be outright rejected (this includes sane-but-unusual formats for screenshots and icons, such as TIFF images).
The Compose library, with all of these changes, will now just request high-level operations (e.g. “render a font card for this font to a JXL image”) from the worker, and provide it with input data in sealed memfds and output locations as FDs as well. On Linux systems, the worker will use Landlock if available, to block all write access to the filesystem, deny device access and deny TCP and UDP as well. The sandbox can certainly be tightened a fair bit in future, but this was a good and safe start to gain some experience with it without having things break too easily, given the many places Compose is used in (also, Landlock’s API is surprisingly nice to use, so it was easier than I thought to add in this early version).
With all of these changes, the libappstream-compose library is now also officially marked API-stable, so you should be able to rely on it in future to build new things (its API has barely changed in the past, and now with the new media API and defaults change in place, it was time to declare it stable).
I want to see / try this!
Currently, the easiest way to have a look at the new data is to check out Debian Unstable. If you have a JXL-enabled browser, you can also see the icons in AppStream Generator’s HTML pages for Debian Sid. If you are using appstream-generator for your distribution, you will also get much more pleasant statistics and HTML pages, as well as fully deterministic media output and a whole bunch of security updates, so, update to its recent 1.0 release.
Please keep in mind that if you switch to JXL, the client tools receiving the image data have to support it. Support varies depending on the Linux distribution, so, test it first and switch the default back to PNG in case you encounter any issues.
What’s next?
With so many features and changes landed, the next changes in AppStream will focus on improving what already exists and fixing any issues (there will be more blogposts about the other features 1.2.x delivers!). Testing with the entire Debian archive as data source makes me fairly confident though that there will not be many problems. In the longer term, tightening the media processing sandbox will also be something we might want to do, e.g. by hiding parts of the filesystem tree or filtering syscalls.
For JPEG-XL, one obvious question is “Will you add support for it to the Freedesktop icon-theme specification as supported format alongside PNG, SVG(Z), and XPM?”. For on-disk icon repositories, JXL’s space-savings are less compelling, and it being HDR-capable is also not necessarily a killer feature (PNG can go a long way!). However, JPEG-XL’s ability to immediately decode larger images at reduced resolution without resampling could legitimately be very powerful here, as applications could ship a single large image and quickly decode it at 1/2, 1/4 or 1/8 the size for different purposes in their UI. JPEG-XL also supports spot-color extra channels, which applications could use as masks to recolor raster icons at render time. This could be incredibly nice to color symbolic icons on-the-fly without any SVG and CSS. JXL also provides richer metadata, which might be neat for (license/author) documentation. So, the answer here is: Maybe it makes sense to allow another format, but this will have to be discussed first, as it would force JXL into every toolkit and desktop, which is a much bigger ask than supporting it only in AppStream.
As always, let me know what you think and please report any issues or bugs directly against AppStream or AppStream Generator if you encounter problems that are with the tools, and not with a project’s metadata.
An new maintenance version 0.2.8 of the random-number generator
tester RDieHarder
(based on the DieHarder
suite developed / maintained by Robert Brown with contributions by David
Bauer and myself along with other contributors) is now on CRAN and available via r2u.
This release contains only internal maintenance changes: continuous
integration was updated a few times, newer nags from R are addressed in
Rd files and the vignette, and we also updated a few URLs in the
vignette and README.me. No new code, no new features.
podlators is the package containing Pod::Man, Pod::Text, and other
tools for converting POD documentation into manual pages and simple
text documents.
This release fixes a long-standing bug in Pod::Text and subclasses
where a pathological level of indentation could cause the wrapping
code to go into an infinite loop. Thanks to Jitka Plesnikova for
the report. This was assigned CVE-2026-82560, although I make no
guarantees that podlators is safe to run on untrusted input and
therefore not fully treating this like a security issue.
While fixing that bug, I noticed a bug in Pod::Text::Overstrike's
wrapping code that would leave stray formatting at the start of the
next line in some situations. That is also fixed in this release.
I’m looking at something entirely unrelated, but tripped over some search results that made me realise that a lot of people still think getting errors like ACPI Warning: SystemIO range 0x0000000000001828-0x000000000000182F conflicts with OpRegion 0x0000000000001800-0x000000000000187F indicate a firmware bug. This is generally untrue. We need to dive a little into what ACPI is to clarify why.
The Advanced Configuration and Power Interface1 specification defines a whole bunch of stuff, but what’s interesting to us here is the hardware abstraction it performs. While PCs are nominally a well-defined platform that’s really not true at the hardware level once you get beyond a certain level of complexity. When you suspend a system you want to power down the hardware in the correct order, for instance, and knowing what that order is requires you to know details about the specific motherboard design. The approach taken in the embedded world is to just bake that knowledge into the OS in some form, which is how we end up with Devicetree. ACPI takes an alternative approach - rather than provide that information as data that has to be consumed by OS drivers, it distributes it as code.
The ACPI Source Language, or ASL, is a simple language that gets compiled into a bytecode that’s then interpreted by the OS at runtime. One of the features of this language is the ability to define “Operation Regions”, effectively structure definitions that describe access to underlying hardware. Let’s imagine a simple device with two exposed registers. The first is an index register - it describes which internal register we want to access. The second is a data register, where reading it gives us the value of the internal register whose address is currently in the index register, and writing to it modifies that register. An example operation region declaration would look something like
This defines an operation region called “OPR1” at IO port 0x400, 2 bytes long. Inside it are two 8-bit fields, INDX and DATA. These are to be accessed one at a time, do not need the ACPI interpreter to take a global lock when accessing them, and if a subset of the register is modified then the other values should be preserved (irrelevant in this case since the fields are only a byte wide). Now any references to INDX or DATA in this scope will trigger accesses to those registers. So, a method to read the value of register 0x03 would look something like:
1
2
3
4
Method (RD03) {
INDX = 0x3
Return (DATA)
}
ie, set INDX to 3, and then read the value of DATA and return it. But! What if another ACPI method is running at the same time? Let’s say we have one that writes to register 0x05:
1
2
3
4
Method (WR05, 1) {
INDX = 0x05
DATA = Arg1
}
What happens if RD03 executes while we’re part-way through WR05? INDX might get reset to 0x03, and now WR05 will modify register 0x03 instead of 0x05. Oh no! But we can avoid this - we declare a mutex (Mutex (MUTX, 0x00)), and update our methods to be something like:
Each method takes a lock (waiting up to 0xffff milliseconds and then erroring out if it doesn’t), and performs the access. There’s now no chance of a race. Phew!
Now suppose someone writes a Linux driver for this piece of hardware. It accesses the hardware directly, with no knowledge of ACPI. What stops the driver from racing against one of the ACPI access methods? Nothing at all. Oh no! Again! This isn’t hypothetical, by the way - here’s a relatively harmless example, but back in the day we did trip over cases where temperature monitoring chips would be accessed by the firmware and Linux simultaneously and as a result you might end up thinking you’re reading a temperature when you’re actually reading a status flag, resulting in an impossibly high temperature and an immediate thermal shutdown.
In this case, the kernel saves you from this (potentially hardware damaging) outcome by printing a message like ACPI Warning: SystemIO range 0x0000000000000400-0x000000000000401 conflicts with OpRegion 0x0000000000000400-0x0000000000000401 (OPR1), telling you that the kernel has detected that a driver is attempting to allocate IO ports 0x400-0x401, but that there’s an ACPI operation region called OPR1 that is claiming the same addresses. The kernel isn’t in a position to know what type of access the firmware might perform in that region, so assumes that it might be dangerous and blocks the driver from loading.
But all is not lost! The kernel also prints some helpful advice, ACPI: If an ACPI driver is available for this device, you should use it instead of the native driver. And ACPI tables will often actually have a definition that looks like this:
which defines an ACPI device and associated methods. The _HID field defines the device type, and a Linux driver can be written that will be automatically loaded if a device with type VEND0001 is seen. That driver can then call ACPI methods associated with the device and access the resources in a way that matches the firmware’s expectations.
(Interested in writing such a driver? I wrote a guide back in 2009)
The firmware did absolutely nothing wrong here2, but trying to load the native driver will generate an error and the internet will tell you that PC firmware developers are incompetent3 and you should pass a kernel argument that overrides this behaviour and it never did them any harm, and it probably won’t do you any harm either but it might and you might never know why your system occasionally wedges or catches fire.
The ACPI spec used to live at acpi.info, but sadly that seems to have vanished some time after UEFI took over stewardship of the spec ↩︎
You might argue that the firmware should simply not do anything at runtime because it is not the firmware’s job to do that, and I do understand that and you can certainly boot with acpi=off if you want to and no ACPI code will be executed at runtime. Let me know how that goes. ↩︎
I’m not going to present an opinion on that here, merely say that this provides no supporting evidence for that assertion ↩︎