• Pl chevron_right

      Aryan Kaushik: Open Forms is now 0.4.0 - and the GUI Builder is here

      news.movim.eu / PlanetGnome • 2 months from now • 3 minutes

    Open Forms is now 0.4.0 - and the GUI Builder is here

    A quick recap for the newcomers

    Ever been to a conference where you set up a booth or tried to collect quick feedback and experienced the joy of:

    • Captive portal logout
    • Timeouts
    • Flaky Wi-Fi drivers on Linux devices
    • Poor bandwidth or dead zones

    Meme showcasing wifi fails when using forms

    This is exactly what happened while setting up a booth at GUADEC. The Wi-Fi on the Linux tablet worked, we logged into the captive portal, the chip failed, Wi-Fi gone. Restart. Repeat.

    Meme showing a person giving their child a book on 'Wifi drivers on linux' as something to cry about

    We eventually worked around it with a phone hotspot, but that locked the phone to the booth. A one-off inconvenience? Maybe. But at any conference, summit, or community event, at least one of these happens reliably.

    So I looked for a native, offline form collection tool. Nothing existed without a web dependency. So I built one.

    Open Forms is a native GNOME app that collects form inputs locally, stores responses in CSV, works completely offline, and never touches an external service. Your data stays on your device. Full stop.

    Open Forms pages

    What's new in 0.4.0 - the GUI Form Builder

    The original version shipped with one acknowledged limitation: you had to write JSON configs by hand to define your forms.

    Now, I know what you're thinking. "Writing JSON to set up a form? That's totally normal and not at all a terrible first impression for non-technical users." And you'd be completely wrong, to me it was normal and then my sis had this to say "who even thought JSON for such a basic thing is a good idea, who'd even write one" which was true. I knew it and hence it was always on the roadmap to fix, which 0.4.0 finally fixes.

    Open Forms now ships a full visual form builder.

    Design a form entirely from the UI - add fields, set labels, reorder things, tweak options, and hit Save. That's it. The builder writes a standard JSON config to disk, same schema as always, so nothing downstream changes.

    It also works as an editor. Open an existing config, click Edit, and the whole form loads up ready to tweak. Save goes back to the original file. No more JSON editing required.

    Open forms builder page

    Libadwaita is genuinely great

    The builder needed to work well on both a regular desktop and a Linux phone without me maintaining two separate layouts or sprinkling breakpoints everywhere. Libadwaita just... handles that.

    The result is that Open Forms feels native on GNOME and equally at home on a Linux phone, and I genuinely didn't have to think hard about either. That's the kind of toolkit win that's hard to overstate when you're building something solo over weekends.


    The JSON schema is unchanged

    If you already have configs, they work exactly as before. The builder is purely additive, it reads and writes the same format. If you like editing JSON directly, nothing stops you. I'm not going to judge, but my sister might.

    Also thanks to Felipe and all others who gave great ideas about increasing maintainability. JSON might become a technical debt in future, and I appreciate the insights about the same. Let's see how it goes.

    Install

    Snap Store

    snap install open-forms
    

    Flatpak / Build from source

    See the GitHub repository for build instructions. There is also a Flatpak release available .

    What's next

    • A11y improvements
    • Maybe and just maybe an optional sync feature
    • Hosting on Flathub - if you've been through that process and have advice, please reach out

    Open Forms is still a small, focused project doing one thing. If you've ever dealt with Wi-Fi pain while collecting data at an event, give it a try. Bug reports, feature requests, and feedback are all very welcome.

    And if you find it useful - a star on GitHub goes a long way for a solo project. 🙂

    Open Forms on GitHub

    • Pl chevron_right

      Jakub Steiner: Revert That Vector Nonsense!

      news.movim.eu / PlanetGnome • 0:00

    A few years back I did a quick exploration of what GNOME app icons might look like in an alternate universe where we kept on using VGA displays. Chiselling pixels away is therapeutic. So while there is absolutely no use for these, I keep on making them if only to bring some attention to what really matters for GNOME, having nice apps.

    Here's a batch of mostly GNOME Circle app icons, with some 3rd party ones thrown in.

    Pixel art GNOME app icons, batch 1 Pixel art GNOME app icons, batch 2 Pixel art GNOME app icons, batch 3 Pixel art GNOME app icons, batch 4 Pixel art GNOME app icons, batch 5 Pixel art GNOME app icons, batch 6 Pixel art GNOME app icons, batch 7

    If you're reading this on my site rather than Planet GNOME or some flickering terminal in an abandoned Vault, then congratulations. You've stumbled upon a working Pip-Boy module! Found it half-buried under irradiated rubble, its phosphor display still humming with that familiar green glow. Enjoy these icons the way the dwellers of Vault 101 were always meant to, one glorious scanline at a time.

    • Pl chevron_right

      Michael Catanzaro: git config am.threeWay

      news.movim.eu / PlanetGnome • 22 hours ago • 1 minute

    If you work with patches and git am , then you’re probably used to seeing patches fail to apply. For example:

    $ git am CVE-2025-14512.patch
    Applying: gfileattribute: Fix integer overflow calculating escaping for byte strings
    error: patch failed: gio/gfileattribute.c:166
    error: gio/gfileattribute.c: patch does not apply
    Patch failed at 0001 gfileattribute: Fix integer overflow calculating escaping for byte strings
    hint: Use 'git am --show-current-patch=diff' to see the failed patch
    hint: When you have resolved this problem, run "git am --continue".
    hint: If you prefer to skip this patch, run "git am --skip" instead.
    hint: To restore the original branch and stop patching, run "git am --abort".
    hint: Disable this message with "git config set advice.mergeConflict false"

    This is sad and frustrating because the entire patch has failed, and now you have to apply the entire thing manually. That is no good.

    Here is the solution, which I wish I had learned long ago:

    $ git config --global am.threeWay true

    This enables three-way merge conflict resolution, same as if you were using git cherry-pick or git merge . For example:

    $ git am CVE-2025-14512.patch
    Applying: gfileattribute: Fix integer overflow calculating escaping for byte strings
    Using index info to reconstruct a base tree...
    M	gio/gfileattribute.c
    Falling back to patching base and 3-way merge...
    Auto-merging gio/gfileattribute.c
    CONFLICT (content): Merge conflict in gio/gfileattribute.c
    error: Failed to merge in the changes.
    Patch failed at 0001 gfileattribute: Fix integer overflow calculating escaping for byte strings
    hint: Use 'git am --show-current-patch=diff' to see the failed patch
    hint: When you have resolved this problem, run "git am --continue".
    hint: If you prefer to skip this patch, run "git am --skip" instead.
    hint: To restore the original branch and stop patching, run "git am --abort".
    hint: Disable this message with "git config set advice.mergeConflict false"

    Now you have merge conflicts, which you can handle as usual. This seems like a better default for pretty much everybody, so if you use git am , you should probably enable it.

    I’ve no doubt that many readers will have known about this already, but it’s new to me, and it makes me happy, so I wanted to share. You’re welcome, Internet!

    • Pl chevron_right

      Jonathan Blandford: Goblint Notes

      news.movim.eu / PlanetGnome • 1 day ago • 2 minutes

    I was excited to see Bilal’s announcement of goblint, and I’ve spent the past week getting Crosswords to work with it. This is a tool I’ve always wanted and I’m pretty convinced it will be a great boon for the GNOME ecosystem. I’m posting my notes in hope that more people try it out:

    • First and most importantly, Bilal has been so great to work with. I have filed ~20 issues and feature requests and he fixed them all very quickly. In some cases, he fixed the underlying issue before I completed adding annotations to the code.
    • Most of the issues flagged were idiomatic and stylistic, but it did find real bugs. It found a half-dozen leaks, a missing g_timeout removal, and five missing class function chain ups. One was a long-standing crasher. There’s a definite improvement in quality from adopting this tool.
    • I’m also excited about pairing this with new GSoC interns. The types of things goblint flags are the things that students hit in particular (when they don’t write it all their code with AI). I think goblint will be even more important to our ecosystem as a teaching tool to our C codebase. It’s already effectively replaced my styleguide.
    • In a few instances, the use_g_autoptr rule outstripped static-scan’s ability to track leaks. Ultimately, I ended up annotating and removing the g_autoptr() calls as I couldn’t get the two to play nicely together.
    • Along the same lines, cairo, pango, and librsvg all lack G_DEFINE_AUTOPTR_CLEANUP_FUNC . It would be really great if we could fix these core libraries. In the meantime, you can add the following to your project’s goblint.toml file:
    [rules.use_g_autoptr_inline_cleanup]
    level = "error"
    ignore_types = ["cairo_*", "Pango*", "RsvgHandle"]
    
    • I had some trouble getting the pipeline integrated with GNOME’s gitlab. The gitlab recipe on his page uses premium features unavailable in the self hosted version. If it’s helpful for others, here’s what I ended up using:
    goblint:
      stage: analysis
      extends:
        - "opensuse-container@x86_64.stable"
        - ".fdo.distribution-image@opensuse"
      needs:
        - job: opensuse-container@x86_64.stable
          artifacts: false
      before_script:
        - source ci/env.sh
        - cargo install --git https://github.com/bilelmoussaoui/goblint goblint
      script:
        # Goblint is fast. We run it twice: Once to generate the report,
        # and a second time to display the output and triger an error
        - /root/.cargo/bin/goblint . --format sarif > goblint.sarif || true
        - /root/.cargo/bin/goblint . --format text
      artifacts:
        reports:
          sast: goblint.sarif
        when: always

    YMMV

    • Pl chevron_right

      This Week in GNOME: #246 Offline Dictionaries

      news.movim.eu / PlanetGnome • 1 day ago • 6 minutes

    Update on what happened across the GNOME project in the week from April 17 to April 24.

    GNOME Core Apps and Libraries

    Libadwaita

    Building blocks for modern GNOME apps using GTK4.

    Alice (she/her) 🏳️‍⚧️🏳️‍🌈 says

    libadwaita demo runs on android now, and apk files can be grabbed from CI

    Alice (she/her) 🏳️‍⚧️🏳️‍🌈 reports

    AdwSidebar and AdwViewSwitcherSidebar now allow adding widgets above and below their content. This can be used to add things like account switchers

    Third Party Projects

    Haydn Trowell says

    Kotoba, a fast, fully offline Japanese–English dictionary, is now available on Flathub.

    Key features:

    • Flexible search: Look up words using kanji, kana, rōmaji, or English meanings
    • Responsive results: Matches appear almost instantly
    • Detailed entries: Readings, meanings, example sentences, and usage notes where available
    • Smart conjugation handling: Recognizes inflected verb and adjective forms and maps them to their base entries
    • Bookmarks: Save words to review later
    • Fully offline: Works without an internet connection

    Get it on Flathub: https://flathub.org/apps/net.trowell.kotoba

    kotoba-entries.B2m-08A3_1x2Mzi.webp

    Antonio Zugaldia says

    Stargate is a new Java and Kotlin library that gives JVM applications access to XDG Desktop Portals on Linux.

    • Full coverage of the portal spec, including Global Shortcuts, Remote Desktop, Notification, and Settings.
    • Adds system tray icon support via the XDG Status Notifier Item specification.
    • Ships with a demo app built using Java GI, the GTK/GNOME Java bindings (come say hi at #java-gi:matrix.org ).

    Available on Maven Central. More at https://github.com/zugaldia/stargate .

    Bilal Elmoussaoui reports

    goblint has received a lot of work lately. Supporting 22 new rules since the last update and a webpage displaying the list of the rules and the available per-rule configurations https://bilelmoussaoui.github.io/goblint/ . The page can include an extensive documentation like the following https://bilelmoussaoui.github.io/goblint/#use_g_autoptr_inline_cleanup but that is the only rule having that for now.

    Jan-Michael Brummer reports

    Take control of your health with Blood Pressure 1.0.0.

    Released just a month ago, this powerful yet easy-to-use app helps you track systolic and diastolic blood pressure as well as pulse with precision. Visualize your progress through clear, intuitive charts and gain valuable insights with in-depth statistics based on ESH/ESC guidelines.

    Log measurements effortlessly with date, time, and optional notes, explore your history at a glance, and benefit from comprehensive analysis designed to support better health decisions. It’s available on Flathub: https://flathub.org/en/apps/org.tabos.bloodpressure

    bloodpressure1.CxZTe89g_2myOWS.webp

    bloodpressure2.3zsPqlbE_Z13scdY.webp

    Nathan Perlman says

    After a few months of on-and-off development, Rewaita v1.1.2 has finally been released!

    For anyone who doesn’t know, Rewaita is a customization tool for applying colour schemes to Adwaita and GNOME.

    What’s new:

    • Finally added proper Firefox support, and is also compatible with the Firefox Gnome Theme for those who use it.
    • More customization options, including three more window control themes, and a new colour scheme (Gruvbox Hard).
    • Now includes an option to force light text in the app overview, which might be useful to blur-my-shell users.
    • Some quality of life improvements like a better user guide, and bug fixes with significantly less crashing.
    • Added Chinese Simplified translations.

    Thanks to everyone who helped out with this release! Available on Flathub as always, as well as the AUR. More at https://github.com/swordpuffin/Rewaita .

    adwaita_theming.Dx-T6VFi_sc0IC.webp

    adwaita_theming_2.DUBOpUbD_2eOHFs.webp

    Solitaire

    Play Patience Games

    Will Warner reports

    Solitaire 50.1 has been released! I want to thank everyone who contributed to this update. Firstly, to the translators who created translations for the app within the first two weeks of it being on Damned Lies. Second, to everyone who submitted and commented on issues, I appreciate your help.

    Here’s what changed: • Added translations: Brazilian Portuguese, Cornish, Kazakh, Serbian, Swedish, Ukrainian, Slovenian, Russian • Added categories to the desktop entry • Added all of the missing Aisleriot card themes except for Guyenne Classic • Fixed a bug where re-deals weren’t decremented upon undoing them • Fixed a bug that allowed one too many re-deals • Clarified the uses of the solver in the preferences • Added a ‘Display the Winnability Warning’ option to the preferences • Made the theme refresh when the theme selector dialog is closed • Adjusted the brand colors to not clash with the window colors • Added spacing between cards and margins for the playing area • Added a ‘New Game’ option to the ‘Impossible to win’ dialog • Added a fail-safe for GTK not handling drags correctly on X11

    Get it on Flathub

    Pipeline

    Follow your favorite video creators.

    schmiddi reports

    Pipeline version 4.0.0 was released this week! This release overhauls downloading videos. Instead of depending on an external program to download videos, Pipeline now has downloading videos built-in. This also includes watching downloaded videos directly in the application. Besides that, you can now inspect your watch history in the application. There were further minor additions and improvements, which you can read about in the changelog of this release .

    Note that this release also contains a breaking change for Pipeline users using external video players: By default, the Flatpak on Flathub does not have the permission to spawn external video players anymore. If you want to continue using external players instead of the built-in video player, you will to manually grant Pipeline this permission, as detailed in the preferences dialog of Pipeline or in this wiki page .

    Parabolic

    Download web video and audio.

    Nick reports

    Parabolic V2026.4.0 is here!

    This release contains many bug fixes, new features and design improvements making Parabolic an even better app! This release also includes a new macOS version of Parabolic - expanding our userbase!

    A huge thank you to everyone who has tested and reported bugs throughout this development cycle. ❤️

    Here’s the full changelog:

    • Added macOS app for the GNOME version of Parabolic
    • Added Windows portable version of Parabolic
    • Added the ability to toggle super resolution formats in Parabolic’s settings
    • Added the ability to specify a preferred frame rate for video downloads in Parabolic’s settings
    • Added the ability to toggle immediate audio or video downloads separately
    • Added the ability to automatically translate embedded metadata and chapters to the app’s language on supported sites. This can be turned off in Converter settings
    • Added the ability to update deno from within the app
    • Added thumbnail image preview to add download dialog and downloads view
    • Added failed filter to downloads view
    • Added total duration label to playlist items view
    • Improved Parabolic’s startup time by using NativeAOT compilation
    • Improved selection of playlist video formats when resolutions are specified
    • Improved selection of playlist audio formats on Windows when bitrates are specified
    • Improved cropping of audio thumbnails
    • Improved handling of long file names, they will now be truncated if too long
    • Removed unsupported cookie browsers on Windows. Manual txt files should be used instead
    • Fixed an issue where download progress did not show correctly
    • Fixed an issue where the preferred video codec was ignored when a preferred frame rate was also set
    • Fixed an issue where the exported M3U playlist file would contain duplicate entries
    • Fixed an issue where credentials would not save on Linux
    • Fixed an issue where batch files were unusable on Linux and macOS
    • Fixed an issue where uploading a cookies file did not work on Windows
    • Fixed an issue where time frame downloads would not complete on Windows
    • Fixed an issue where certain video formats would process infinitely on Windows
    • Updated yt-dlp

    That’s all for this week!

    See you next week, and be sure to stop by #thisweek:gnome.org with updates on your own projects!

    • Pl chevron_right

      Sam Thursfield: Status update, 23rd April 2026

      news.movim.eu / PlanetGnome • 2 days ago • 6 minutes

    Hello there,

    You thought I’d given up on “status update” blog posts, did you ? I haven’t given up, despite my better judgement, this one is just even later than usual.

    Recently I’ve been using my rather obscure platform as a blogger to theorize about AI and the future of the tech industry, mixed with the occasional life update, couched in vague terms, perhaps due to the increasing number of weirdos in the world who think doxxing and sending death threats to open source contributors is a meaningful use of their time.

    In fact I do have some theories about how George Orwell (in “Why I Write” ) and Italo Calvino (in “If On a Winter’s Night a Traveller” ) made some good guesses from the 20th century about how easy access to LLMs would affect communication, politics and art here in the 21st. But I’ll leave that for another time.

    It’s also 8 years since I moved to this new country where I live now, driving off the boat in a rusty transit van to enjoy a series of unexpected and amazing opportunities. Next week I’m going to mark the occasion with a five day bike ride through the mountains of Asturias, something I’ve been dreaming of doing for several years.

    The original idea of writing a monthly post was to keep tabs on various open source software projects I sometimes manage to contribute to, and perhaps even to motivate me to do more such volunteering. Well that part didn’t work, house renovations and an unexpectedly successful gig playing synth and trombone took over all my free time; but after many years of working on corporate consultancy and doing a little open source in the background, I’m trying to make a space at work to contribute in the open again.

    I could tell the whole story here of how Codethink became “the build system people”. Maybe I will actually. It all started with BuildStream. In fact, that’s not even true. it all started in 2011 when some colleagues working with MeeGo and Yocto thought, “This is horrible, isn’t it?”

    They set out to create something better, and produced Baserock, which unfortunately turned out even worse. But it did have some good ideas. The concept of “cache keys” to identify build inputs and content-addressed storage to hold build outputs began there, as did the idea of opening a “workspace” to make drive-by changes in build inputs within a large project.

    BuildStream took this core idea, extended it to support arbitrary source kinds and element kinds defined by plugins, and added a shiny interface on top. It used OSTree to store and distribute build artifacts initially, later migrating to the Google REAPI with the goal of supporting Enterprise(TM) infrastructure. You can even use it alongside Bazel, if you like having three thousand commandline options at your disposal.

    Unfortunately it was 2016, so we wrote the whole thing in Python. (In our defence, the Rust programming language had only recently hit 1.0 and crates.io was still a ghost town, and we’d probably still be rewriting the ruamel.yaml package in Rust if we had taken that road.) But the company did make some great decisions, particularly making a condition of success for the BuildStream project that it could unify the 5 different build+integration systems that GNOME release team were maintaining. And that success meant not making a prototype, but the release team actually using BuildStream to make releases. Tristan even ended up joining the GNOME release team for a while. We discussed it all at the 2017 Manchester GUADEC, coincidentally. It was a great time. (Aside from the 6 months leading up to the conference .)

    At this point, the Freedesktop SDK already existed, with the same rather terrible name that it has today, and was already the base runtime for this new app container tool that was named… xdg-app . (At least that eventually gained a better name). However, if you can remember 8 years ago, it had a very different form than today. Now, my memory of what happened next is especially hazy at this point, because like I told you in the beginning, I was on a boat with my transit van heading towards a new life in Spain. All I have to go on 8 years later is the Git history , but somehow the Freedesktop SDK grew a 3-stage compiler bootstrap, over 600 reusable BuildStream elements, its own Gitlab namespace, and even some controversial stickers. As a parting gift I apparently added support for building VMs , the idea being that we’d reinstate the old GNOME Continuous CI system that had unfortunately died of neglect several years earlier. This idea got somewhat out of hand, let’s say.

    It took me a while to realize this, but today Freedesktop SDK is effectively the BuildStream reference distribution. What Poky is to BitBake in the Yocto project, this is what Freedesktop SDK is to BuildStream. And this is a pretty important insight. It explains the problem you may have experienced with the BuildStream documentation: you want to build some Linux package, so you read through the manual right to the end, and then you still have no fucking idea how to integrate that package.

    This isn’t a failure on the part of the authors, instead the issue is that your princess is in another castle. Every BuildStream project I’ve ever worked on has junctioned freedesktop-sdk.git and re-used the elements, plugins, aliases, configurations and conventions defined there, all of which are rigorously undocumented. The Freedesktop SDK Guide , for reasons that I won’t go into, doesn’t venture much further than than reminding you how to call Make targets.

    And this is something of a point of inflection. The BuildStream + Freedesktop SDK ecosystem has clearly not displaced Yocto, nor for that matter Linux Mint. But, like many of my favourite musicians , it has been quietly thriving in obscurity. People I don’t know are using it to do things that I don’t completely understand. I’ve seen it in comparison articles, and even job adverts. ChatGPT can generate credible BuildStream elements about as well as it can generate Dockerfiles (i.e. not very well, but it indicates a certain level of ubiquity). There have been conferences, drama, mistakes, neglect. It’s been through an 8 person corporate team hyper-optimizing the code, and its been though a mini dark age where volunteers thanklessly kept the lights on almost single handledly, and its even survived its transition to the Apache Foundation.

    Through all of this, the secret to its success probably that its just a really nice tool to work with . As much as you can enjoy software integration, I enjoy using BuildStream to do it; things rarely break, when they do its rarely difficult to fix them, and most importantly the UI is really colourful! I’m now using it to build embedded system images for a product named CTRL , which you can think of as.. a Linux distribution. There are some technical details to this which I’m working to improve, which I won’t bore you with here.

    I also won’t bore you with the topic of community governance this month, but that’s what’s currently on my mind. If you’ve been part of the GNOME Foundation for a few years, you’ll know this something that’s usually boring and occasionally becomes of almost life-or-death importance. The “let’s just be really sound” model works great, until one day when you least expect it, and then suddenly it really doesn’t. There is no perfect defence against this, and in open source communities its our diversity that brings the most resilience. When GNOME loses, KDE gains, and that way at least we still don’t have to use Windows. Indeed, this is one argument for investing in BuildStream even if it remains forever something of a minority spot. I guess I just need to remember that when you have to start thinking hard about governance, that’s a sign of success.

    • Pl chevron_right

      Sebastian Wick: How Hard Is It To Open a File?

      news.movim.eu / PlanetGnome • 2 days ago • 10 minutes

    It’s a question I had to ask myself multiple times over the last few months. Depending on the context the answer can be:

    • very simple, just call the standard library function
    • extremely hard, don’t trust anything

    If you are an app developer, you’re lucky and it’s almost always the first answer. If you develop something with a security boundary which involves files in any way, the correct answer is very likely the second one.

    Opening a File, the Hard Way

    Like so often, the details depend on the specifics, but in the worst-case scenario, there is a process on either side of the security boundary, which operate on a filesystem tree which is shared by both processes.

    Let’s say that the process with more privileges operates on a file on behalf of the process with less privileges. You might want to restrict this to files in a certain directory, to prevent the less privileged process from, for example, stealing your SSH key, and thus take a subpath that is relative to that directory.

    The first obvious problem is that the subpath can refer to files outside of the directory if it contains .. . If the privileged process gets called with a subpath of ../.ssh/id_ed25519 , you are in trouble. Easy fix: normalize the path, and if we ever go outside of the directory, fail.

    The next issue is that every component of the path might be a symlink. If the privileged process gets called with a subpath of link , and link is a symlink to ../.ssh/id_ed25519 , you might be in trouble. If the process with less privileges cannot create files in that part of the tree, it cannot create a malicious symlink, and everything is fine. In all other scenarios, nothing is fine. Easy fix: resolve the symlinks, expand the path, then normalize it.

    This is usually where most people think we’re done, opening a file is not that hard after all, we can all do more fun things now. Really, this is where the fun begins.

    The fix above works, as long as the less privileged process cannot change the file system tree anywhere in the file’s path while the more privileged process tries to access it. Usually this is the case if you unpack an attacker-provided archive into a directory the attacker does not have access to. If it can however, we have a classic TOCTOU (time-of-check to time-of-use) race.

    We have the path foo/id_ed25519 , we resolve the smlinks, we expand the path, we normalize it, and while we did all of that, the other process just replaced the regular directory foo that we just checked with a symlink which points to ../.ssh . We just checked that the path resolves to a path inside the target directory though, and happily open the path foo/id_ed25519 which now points to your ssh key. Not an easy fix.

    So, what is the fundamental issue here? A path string like /home/user/.local/share/flatpak/app/org.example.App/deploy describes a location in a filesystem namespace. It is not a reference to a file. By the time you finish speaking the path aloud, the thing it names may have changed.

    The safe primitive is the file descriptor. Once you have an fd pointing at an inode, the kernel pins that inode. The directory can be unlinked, renamed, or replaced with a symlink; the fd does not care. A common misconception is that file descriptors represent open files. It is true that they can do that, but fds opened with O_PATH do not require opening the file, but still provide a stable reference to an inode.

    The lesson that should be learned here is that you should not call any privileged process with a path. Period. Passing in file descriptors also has the benefit that they serve as proof that the calling process actually has access to the resource.

    Another important lesson is that dropping down from a file descriptor to a path makes everything racy again. For example, let’s say that we want to bind mount something based on a file descriptor, and we only have the traditional mount API, so we convert the fd to a path, and pass that to mount. Unfortunately for the user, the kernel resolves the symlinks in the path that an attacker might have managed to place there. Sometimes it’s possible to detect the issue after the fact, for example by checking that the inode and device of the mounted file and the file descriptor match.

    With that being said, sometimes it is not entirely avoidable to use paths, so let’s also look into that as well!

    In the scenario above, we have a directory in which we want all the paths to resolve in, and that the attacker does not control. We can thus open it with O_PATH and get a file descriptor for it without the attacker being able to redirect it somewhere else.

    With the openat syscall, we can open a path relative to the fd we just opened. It has all the same issues we discussed above, except that we can also pass O_NOFOLLOW . With that flag set, if the last segment of the path is a symlink, it does not follow it and instead opens the actual symlink inode. All the other components can still be symlinks, and they still will be followed. We can however just split up the path, and open the next file descriptor for the next path segment and resolve symlinks manually until we have done so for the entire path.

    libglnx chase

    libglnx is a utility library for GNOME C projects that provides fd-based filesystem operations as its primary API. Functions like glnx_openat_rdonly , glnx_file_replace_contents_at , and glnx_tmpfile_link_at all take directory fds and operate relative to them. The library is built around the discipline of “always have an fd, never use an absolute path when you can use an fd.”

    The most recent addition is glnx_chaseat , which provides safe path traversal, and was inspired by systemd’s chase() , and does precisely what was described above.

    int glnx_chaseat (int              dirfd,
                      const char      *path,
                      GlnxChaseFlags   flags,
                      GError         **error);
    

    It returns an O_PATH | O_CLOEXEC fd for the resolved path, or -1 on error. The real magic is in the flags:

    typedef enum _GlnxChaseFlags {
      /* Default */
      GLNX_CHASE_DEFAULT = 0,
      /* Disable triggering of automounts */
      GLNX_CHASE_NO_AUTOMOUNT = 1 << 1,
      /* Do not follow the path's right-most component. When the path's right-most
       * component refers to symlink, return O_PATH fd of the symlink. */
      GLNX_CHASE_NOFOLLOW = 1 << 2,
      /* Do not permit the path resolution to succeed if any component of the
       * resolution is not a descendant of the directory indicated by dirfd. */
      GLNX_CHASE_RESOLVE_BENEATH = 1 << 3,
      /* Symlinks are resolved relative to the given dirfd instead of root. */
      GLNX_CHASE_RESOLVE_IN_ROOT = 1 << 4,
      /* Fail if any symlink is encountered. */
      GLNX_CHASE_RESOLVE_NO_SYMLINKS = 1 << 5,
      /* Fail if the path's right-most component is not a regular file */
      GLNX_CHASE_MUST_BE_REGULAR = 1 << 6,
      /* Fail if the path's right-most component is not a directory */
      GLNX_CHASE_MUST_BE_DIRECTORY = 1 << 7,
      /* Fail if the path's right-most component is not a socket */
      GLNX_CHASE_MUST_BE_SOCKET = 1 << 8,
    } GlnxChaseFlags;
    

    While it doesn’t sound too complicated to implement, a lot of details are quite hairy. The implementation uses openat2 , open_tree and openat depending on what is available and what behavior was requested, it handles auto-mount behavior, ensures that previously visited paths have not changed, and a few other things.

    An Aside on Standard Libraries

    The POSIX APIs are not great at dealing with the issue. The GLib/Gio APIs ( GFile , etc.) are even worse and only accept paths. Granted, they also serve as a cross-platform abstraction where file descriptors are not a universal concept. Unfortunately, Rust also has this cross-platform abstraction which is based entirely on paths.

    If you use any of those APIs, you very likely created a vulnerability. The deeper issue is that those path-based APIs are often the standard way to interact with files. This makes it impossible to reason about the security of composed code. You can audit your own code meticulously, open everything with O_PATH | O_NOFOLLOW , chain *at() calls carefully — and then call a third-party library that calls open(path) internally. The security property you established in your code does not compose through that library call.

    This means that any system-level code that cares about filesystem security has to audit all transitive dependencies or avoid them in the first place.

    So what would a better GLib cross-platform API look like? I would say not too different from chaseat() , but returning opaque handles instead of file descriptors, which on Unix would carry the O_PATH file descriptor and a path that can be used for printing, debugging and things like that. You would open files from those handles, which would yield another kind of opaque handle for reading, writing, and so on.

    The current GFile was also designed to implement GVfs: g_file_new_for_uri("smb://server/share/file") gives you a GFile you can g_file_read() just like a local file. This is the right goal, but the wrong abstraction layer. Instead, this kind of access should be provided by FUSE, and the URI should be translated to a path on a specific FUSE mount. This would provide a few benefits:

    • The fd-chasing approach works everywhere because it is a real filesystem managed by the kernel
    • The filesystem becomes independent of GLib and can be used for example from Rust as well
    • It stacks with other FUSE filesystems, such as the XDG Desktop Document Portal used by Flatpak

    Wait, Why Are You Talking About This?

    Nowadays I maintain a small project called Flatpak. Codean Labs recently did a security analysis on it and found a number of issues. Even though Flatpak developers were aware of the dangers of filesystems, and created libglnx because of it, most of the discovered issues were just about that. One of them ( CVE-2026-34078 ) was a complete sandbox escape.

    flatpak run was designed as a command-line tool for trusted users. When you type flatpak run org.example.App , you control the arguments. The code that processes the arguments was written assuming the caller is legitimate. It accepted path strings, because that’s what command-line tools accept.

    The Flatpak portal was then built as a D-Bus service that sandboxed apps could call to start subsandboxes — and it did this by effectively constructing a flatpak run invocation and executing it. This connected a component designed for trusted input directly to an untrusted caller (the sandboxed app).

    Once that connection exists, every assumption baked into flatpak run about caller trustworthiness becomes a potential vulnerability. The fix wasn’t “change one function” — it was “audit the entire call chain from portal request to bubblewrap execution and replace every path string with an fd.” That’s commits touching the portal, flatpak-run , flatpak_run_app , flatpak_run_setup_base_argv , and the bwrap argument construction, plus new options ( --app-fd , --usr-fd , --bind-fd , --ro-bind-fd ) threaded through all of them.

    If the GLib standard file and path APIs were secure, we would not have had this issue.

    Another annoyance here is that the entire subsandboxing approach in Flatpak comes from 15 years ago, when unprivileged user namespaces were not common. Nowadays we could (and should) let apps use kernel-native unprivileged user namespaces to create their own subsandboxes.

    Unfortunately with rather large changes comes a high likelihood of something going wrong. For a few days we scrambled to fix a few regressions that prevented Steam, WebKit, and Chromium-based apps from launching. Huge thanks to Simon McVittie!

    In the end, we managed to fix everything, made Flatpak more secure, the ecosystem is now better equipped to handle this class of issues, and hopefully you learned something as well.

    • Pl chevron_right

      Michael Meeks: 2026-04-21 Tuesday

      news.movim.eu / PlanetGnome • 4 days ago

    • Up early, off to HCL Engage in a football stadium for Richard's keynote, Jason's flashy Domino / AI demo, product management bits, and of course Collabora Online integration announced.
    • Gave talk on COOL, handed out huge numbers of beavers, quick-start guides, stickers and more. Great to talk to lots of excited people engaged with Sovereign alternatives.
    • Dinner in the evening, met more interesting people.
    • Pl chevron_right

      Jussi Pakkanen: CapyPDF is approaching feature sufficiency

      news.movim.eu / PlanetGnome • 4 days ago • 1 minute

    In the past I have written many blog posts on implementing various PDF features in CapyPDF . Typically they explain the feature being implemented, how confusing the documentation is, what perverse undocumented quirks one has to work around to get things working and so on. To save the effort of me writing and you reading yet another post of the same type, let me just say that you can now use CapyPDF to generate PDF forms that have widgets like text fields and radio buttons.

    What makes this post special is that forms and widget annotations were pretty much the last major missing PDF feature Does that mean that it supports everything? No. Of course not. There is a whole bunch of subtlety to consider. Let's start with the fact that the PDF spec is massive , close to 1000 pages. Among its pages are features that are either not used or have been replaced by other features and deprecated.

    The implementation principle of CapyPDF thus far has been "implement everything that needs special tracking, but only to the minimal level needed". This seems complicated but is in fact quite simple. As an example the PDF spec defines over 20 different kinds of annotations. Specifying them requires tracking each one and writing out appropriate entries in the document metadata structures. However once you have implemented that for one annotation type, the same code will work for all annotation types. Thus CapyPDF has only implemented a few of the most common annotations and the rest can be added later when someone actually needs them.

    Many objects have lots of configuration options which are defined by adding keys and values to existing dictionaries. Again, only the most common ones are implemented, the rest are mostly a matter of adding functions to set those keys. There is no cross-referencing code that needs to be updated or so on. If nobody ever needs to specify the color with which a trim box should be drawn in a prepress preview application, there's no point in spending effort to make it happen.

    The API should be mostly done, especially for drawing operations. The API for widgets probably needs to change. Especially since form submission actions are not done. I don't know if anything actually uses those, though. That work can be done based on user feedback.