{"slug": "the-unreasonable-effectiveness-of-vex-in-nixos", "title": "The Unreasonable Effectiveness of Vex in NixOS", "summary": "A new analysis shows that VEX (Vulnerability Exploitability eXchange) statements can be generated automatically for NixOS by combining SBOM-based scanning with reachability analysis, after a sample Go program using golang.org/x/text v0.38.0 triggered two high-severity findings (GO-2026-5970 and GO-2026-6629) that did not affect the code. The two CVEs live only in the golang.org/x/text/unicode/norm and golang.org/x/text/secure/precis packages, neither of which the program imports, so govulncheck reported zero vulnerabilities in the application's own symbols. The approach matters because manual VEX authoring scales with codebase size and must be redone on every code change, while the Go vulnerability database's ecosystem_specific import-path and symbol data lets static analysis narrow reports automatically.", "body_md": "# The Unreasonable Effectiveness of VEX in NixOS\n\nTriggered by the flood of LLM-assisted vulnerability reports this year, we have heard a lot about the struggles that maintainers of popular (open-source) projects face to cope with them. Most projects have slowed down feature development to focus on vulnerability triage; others are still looking for ways to combat the flood itself.\n\nWhile we tend to hear many reports from maintainers, we have heard less about the people on the other side of the process: SysAdmins, SREs and AppSec teams are equally struggling with the same flood. New (potentially unpatched) CVEs are popping up each day. Each CVE needs to be assessed, and affected software needs to be updated or manually patched.\n\nIf you spend any time in this space, it doesn’t take long for you to hear about [VEX](https://www.cisa.gov/sites/default/files/publications/VEX_Use_Cases_Document_508c.pdf) documents and statements. VEX stands for Vulnerability Exploitability eXchange: a machine-readable security advisory that tells you whether a specific software product is actually exposed to a known vulnerability.\n\nSounds great! But can we actually generate these statements in an automated way to support a scalable vulnerability triage process? The answer is, as always: it depends.\n\n## SBOM-Based Scanning\n\nLet’s take a look at a security process that is used in many projects and organizations today.\n\nGiven a simple Go program:\n\n```\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"golang.org/x/text/width\"\n)\n\nfunc main() {\n\tfmt.Println(width.Widen.String(\"hello, world\"))\n}\n```\n\nUsing `v0.38.0` of the `golang.org/x/text` library:\n\n```\nmodule maybe-vulnerable\n\ngo 1.26.8\n\nrequire golang.org/x/text v0.38.0\n```\n\nA common approach to scan a program for vulnerabilities is to:\n\n1. Generate a Software Bill of Materials (SBOM)\n2. Scan the components noted in the SBOM for known vulnerabilities\n\nEvery team using this approach will see the following vulnerabilities (as of 11 Oct 2026):\n\n``` bash\n$ syft scan . -o cyclonedx-json > sbom.cdx\n$ grype sbom:sbom.cdx\nNAME               INSTALLED  FIXED IN  TYPE       VULNERABILITY  SEVERITY  EPSS         RISK\ngolang.org/x/text  v0.38.0    0.39.0    go-module  GO-2026-5970   High      0.5% (39th)  0.4\ngolang.org/x/text  v0.38.0    0.41.0    go-module  GO-2026-6629   High      0.3% (24th)  0.3\n```\n\nIf we do the legwork though, we can see that neither vulnerability actually affects our program.\n\n[GO-2026-5970](https://osv.dev/vulnerability/GO-2026-5970) only exists in the `golang.org/x/text/unicode/norm` package, and [GO-2026-6629](https://osv.dev/vulnerability/GO-2026-6629) only exists in the `golang.org/x/text/secure/precis` package. Neither of them is used in our program!\n\nWe could take this information and craft two VEX statements stating `vulnerable_code_not_present`, but there are two issues with this:\n\n1. This manual process scales with the size of your codebase and the number of dependencies.\n2. You have to **repeat the full analysis every time you change your code** , because you might start to use a vulnerable function.\n\n## Reachability Analysis\n\nLuckily for us, programming languages are highly structured, so it is easy (for machines anyway) to reason about them.\n\nOn top of that, the Go team is very diligent in their work and record every affected import path and symbol, using the [ecosystem_specific field](https://ossf.github.io/osv-schema/#affectedecosystem_specific-field), when a new vulnerability is documented.\n\nThis is what powers [govulncheck](https://go.googlesource.com/vuln). It uses static analysis of our source code and the [Go vulnerability database](https://vuln.go.dev/) to narrow down reports to only those that could affect the application. Running it on our sample application shows:\n\n``` bash\n$ govulncheck .\n=== Symbol Results ===\n\nNo vulnerabilities found.\n\nYour code is affected by 0 vulnerabilities.\nThis scan also found 1 vulnerability in packages you import and 14 vulnerabilities in\nmodules you require, but your code doesn't appear to call these vulnerabilities.\n```\n\nIf you have this power, you could conceivably [turn off Dependabot](https://words.filippo.io/dependabot/), and only patch when affected.\n\n## This Breaks Down for Operating Systems\n\nLet’s see how this would work for an operating system. We use this Dockerfile:\n\n```\nFROM debian:trixie@sha256:913f6706df59a68922d1dd08f78c2476560a8d367897200a6005b00e5f67c2d5\nRUN apt-get update \\\n && apt-get install -y --no-install-recommends openssh-server \\\n && mkdir -p /run/sshd \\\n && rm -rf /var/lib/apt/lists/*\n```\n\nAnd then scan as before:\n\n``` bash\n$ docker build -t opensshpam .\n$ syft scan docker:opensshpam -o cyclonedx-json > sbom.cdx\n$ grype sbom:sbom.cdx\n✔ Scanned for vulnerabilities     [284 vulnerability matches]\n  ├── by severity: 3 critical, 65 high, 80 medium, 39 low, 97 negligible\n  └── by status:   0 fixed, 284 not-fixed, 0 ignored\nNAME             INSTALLED            FIXED_IN   TYPE  VULNERABILITY   SEVERITY    EPSS          RISK\n[...]\nopenssh-server   1:10.0p1-7+deb13u4              deb   CVE-2007-2768   Negligible  8.6% (94th)   0.4\n[...]\n```\n\nFor this demo we will focus on the triage process for a single vulnerability [CVE-2007-2768](https://nvd.nist.gov/vuln/detail/cve-2007-2768), and leave the remaining 283 as an exercise for the reader. 🫠\n\nThe long and short of it is that if sshd uses OPIE one-time passwords through PAM, an attacker can tell from the login prompt whether a username exists. It’s been unfixed upstream since 2007.\n\nManually checking our sshd configuration, we can see that PAM is enabled, but keyboard-interactive authentication is disabled, and that is the only way sshd hands a login prompt to PAM.\n\n``` bash\n$ docker run --rm opensshpam sh -c 'sshd -T | grep -E \"^(usepam|kbdinteractiveauthentication) \"'\nusepam yes\nkbdinteractiveauthentication no\n```\n\nFurthermore, we can see that no module named `opie` is configured.\n\n``` bash\n$ docker run --rm opensshpam grep -E '^(@include|auth)' /etc/pam.d/sshd /etc/pam.d/common-auth\n/etc/pam.d/sshd:@include common-auth\n/etc/pam.d/sshd:@include common-account\n/etc/pam.d/sshd:@include common-session\n/etc/pam.d/sshd:@include common-password\n/etc/pam.d/common-auth:auth     [success=1 default=ignore]      pam_unix.so nullok\n/etc/pam.d/common-auth:auth     requisite                       pam_deny.so\n/etc/pam.d/common-auth:auth     required                        pam_permit.so\n$ docker run --rm opensshpam sh -c 'find / -xdev -name \"pam_opie*\" 2>/dev/null; dpkg -l | grep -i opie'\n# nothing is printed\n```\n\nDoing this for 283 more vulnerabilities on every configuration change is not feasible!\n\n## The Unreasonable Effectiveness of NixOS\n\nNow, what makes NixOS different? Let’s consider this system, which is very close to the Docker-based OS example we had before:\n\n```\n{\n  fileSystems.\"/\" = {\n    device = \"/dev/sda1\";\n    fsType = \"ext4\";\n  };\n  boot.loader.grub.device = \"nodev\";\n  system.stateVersion = \"26.11\";\n\n  services.openssh.enable = true;\n  services.openssh.settings.KbdInteractiveAuthentication = false;\n}\n```\n\nWith the following flake:\n\n```\n{\n  inputs.nixpkgs.url = \"github:NixOS/nixpkgs/nixos-unstable\";\n\n  outputs =\n    { nixpkgs, ... }:\n    {\n      nixosConfigurations.sshd = nixpkgs.lib.nixosSystem {\n        system = \"x86_64-linux\";\n        modules = [\n          ./configuration.nix\n        ];\n      };\n    };\n}\n```\n\nReplacing syft with [sbomnix](https://github.com/tiiuae/sbomnix), we can run the same process as before:\n\n``` bash\n$ sbomnix .#nixosConfigurations.sshd.config.system.build.toplevel\n[...]\nINFO     Wrote: sbom.cdx.json\n$ grype sbom:sbom.cdx.json\n✔ Scanned for vulnerabilities     [272 vulnerability matches]\n  ├── by severity: 14 critical, 84 high, 152 medium, 22 low, 0 negligible\n  └── by status:   147 fixed, 125 not-fixed, 0 ignored\nNAME        INSTALLED    FIXED_IN   TYPE  VULNERABILITY   SEVERITY    EPSS          RISK\n[...]\nopenssh     10.5p1                  nix   CVE-2007-2768   Medium      8.6% (94th)   4.0\n[...]\n```\n\nFinally, we have arrived at the payoff. As we learned before, `CVE-2007-2768` only affects our systems when certain conditions hold, and NixOS allows us to codify those!\n\nNot only that, but we can **conditionally** generate a VEX feed, keeping the VEX statement only if the conditions still hold:\n\n``` js\n{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  notAffected =\n    !(pkgs ? opie)\n    && config.services.openssh.settings.KbdInteractiveAuthentication == false\n    && !(lib.any (rule: rule.enable && lib.hasInfix \"opie\" rule.modulePath) (\n      lib.attrValues config.security.pam.services.sshd.rules.auth\n    ));\nin\n{\n  system.build.vex = pkgs.writeText \"vex.json\" (\n    builtins.toJSON {\n      \"@context\" = \"https://openvex.dev/ns/v0.2.0\";\n      \"@id\" = \"https://kammel.dev/vex/sshd\";\n      author = \"Fabian Kammel\";\n      timestamp = \"2026-10-11T00:00:00Z\";\n      version = 1;\n      statements = lib.optional notAffected {\n        vulnerability.name = \"CVE-2007-2768\";\n        products = [ { \"@id\" = \"pkg:nix/openssh\"; } ];\n        status = \"not_affected\";\n        justification = \"vulnerable_code_not_present\";\n      };\n    }\n  );\n}\n```\n\nIntegrating this into our process:\n\n``` bash\n$ VEX=$(nix build --no-link --print-out-paths .#nixosConfigurations.sshd.config.system.build.vex)\n$ grype --vex $VEX sbom:sbom.cdx.json\n ✔ Scanned for vulnerabilities     [271 vulnerability matches]\n   ├── by severity: 14 critical, 84 high, 152 medium, 22 low, 0 negligible\n   └── by status:   147 fixed, 125 not-fixed, 1 ignored\n```\n\nThe triaged CVE is now successfully ignored.\n\n## nixos-vex\n\nAs you can see, building out these conditions is a lot of work, but the payoff is enormous. Once a CVE has been triaged and codified, the cost to re-check is close to zero (a little bit of CI overhead).\n\nI have been doing this sort of triaging work for my NixOS machines, and I have open-sourced this as [nixos-vex](https://github.com/datosh/nixos-vex).\n\nCheck out the demos for a sample [desktop](https://github.com/datosh/nixos-vex/tree/main/demo/desktop) and [server](https://github.com/datosh/nixos-vex/tree/main/demo/server) profile, as they also highlight some other features built into nixos-vex.\n\n**I would not recommend using this as a high-trust data source yet!**\n\nMy goal is to share the process, invite feedback, and hopefully contribute this back to the Nix ecosystem at some point.", "url": "https://wpnews.pro/news/the-unreasonable-effectiveness-of-vex-in-nixos", "canonical_source": "https://blog.kammel.dev/post/nixos_vex/", "published_at": "2026-10-11 19:23:07+00:00", "updated_at": "2026-10-11 20:30:31.073605+00:00", "lang": "en", "topics": ["ai-safety", "developer-tools", "ai-tools"], "entities": ["NixOS", "VEX", "CISA", "golang.org/x/text", "govulncheck", "Go vulnerability database", "GO-2026-5970", "GO-2026-6629"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/the-unreasonable-effectiveness-of-vex-in-nixos", "markdown": "https://wpnews.pro/news/the-unreasonable-effectiveness-of-vex-in-nixos.md", "text": "https://wpnews.pro/news/the-unreasonable-effectiveness-of-vex-in-nixos.txt", "jsonld": "https://wpnews.pro/news/the-unreasonable-effectiveness-of-vex-in-nixos.jsonld"}}