[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-77376":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":12,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":17,"compositeScore":18,"rankGlobal":9,"rankLanguage":9,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":9,"pushedAt":9,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":15,"starSnapshotCount":15,"syncStatus":17,"lastSyncTime":27,"discoverSource":28},77376,"modulejail","jnuyens\u002Fmodulejail","jnuyens","Proactively shrink a Linux host's kernel-module attack surface by blacklisting every module not currently in use.",null,"Shell",294,11,14,5,0,140,2,3.24,"GNU General Public License v3.0",false,"master",true,[],"2026-06-12 02:03:43","\u003Cp align=\"center\">\n  \u003Cimg src=\"modulejail.png\" alt=\"ModuleJail: lock down unused kernel modules. Reduce risk. Stay secure.\" width=\"480\">\n\u003C\u002Fp>\n\nA single POSIX shell script that shrinks a Linux host's kernel-module attack\nsurface by writing a `modprobe.d` blacklist for every kernel module not\ncurrently in use, minus a built-in baseline and an optional sysadmin\nwhitelist. No daemons, no initramfs changes, no AI inside the tool. One\nscript, one run, one blacklist file.\n\n## Why?\n\nAI-assisted security scanning is about to do to the Linux kernel what\nlarge-scale fuzzing did to userspace code a decade ago, only faster and at a\nmuch larger scale. Many years of latent privilege-escalation bugs in kernel\nmodules are about to surface in quick succession over the coming weeks and\nmonths. Long term, this is a major win for kernel security: every disclosure\ncloses a door that an attacker could otherwise have walked through unseen.\nShort term, it is a nightmare for sysadmins. Every public release brings\nanother race against patch cycles, vendor backports, and reboots across\nthousands of hosts.\n\nModuleJail does not try to fix kernel bugs, and it cannot. It does the one\nthing a sysadmin can do today, on any host, in seconds: shrink the attack\nsurface so that the next disclosed bug is more likely to land on a module the\nhost is not even loading. A typical Linux host ships with several thousand\nkernel modules and uses a few hundred. ModuleJail blacklists the rest. The\nnext CVE in the unused 90% becomes a non-event on that host, and the fleet\noperator buys time to schedule the patch on their own terms instead of\nemergency-paging at 03:00.\n\nThis is intentionally a boring tool. No AI inside it, no daemon, no\ncontinuous monitoring, no risk scoring, no CVE database lookups. Just one\nshell script, run once on a steady-state host, that writes\n`\u002Fetc\u002Fmodprobe.d\u002Fmodulejail-blacklist.conf` to blacklist the thousands of\nunused modules, specific to your system.\n\n## Quickstart\n\n```sh\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fjnuyens\u002Fmodulejail\u002Fv1.2.3\u002Fmodulejail | sudo sh\n```\n\n> **WARNING: convenient, not safe.** This pipes unverified bytes from the\n> network to a root shell. The safer alternative below is the recommended path.\n\n> [!TIP]\n> **On a laptop or workstation? Add `-p desktop`.**\n>\n> The default profile is `conservative` (servers and VMs). It does NOT\n> include WiFi, Bluetooth, audio, or video drivers in the baseline, so\n> if any of those happen not to be loaded at run time (WiFi disconnected,\n> Bluetooth off, headset unplugged, etc.), they may end up blacklisted\n> and unavailable on the next boot. The `desktop` profile keeps them in\n> the keep-list unconditionally.\n>\n> ```sh\n> curl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fjnuyens\u002Fmodulejail\u002Fv1.2.3\u002Fmodulejail | sudo sh -s -- -p desktop\n> ```\n>\n> See [Profiles](#profiles) below for the full list.\n\nThe script writes its blacklist to `\u002Fetc\u002Fmodprobe.d\u002Fmodulejail-blacklist.conf`\nby default. To use a different path:\n\n```sh\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fjnuyens\u002Fmodulejail\u002Fv1.2.3\u002Fmodulejail | sudo sh -s -- -o \u002Fetc\u002Fmodprobe.d\u002Fsite-blacklist.conf\n```\n\n## The safer alternative\n\nDownload, inspect, then run:\n\n```sh\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fjnuyens\u002Fmodulejail\u002Fv1.2.3\u002Fmodulejail -o \u002Ftmp\u002Fmodulejail\nless \u002Ftmp\u002Fmodulejail\nsudo sh \u002Ftmp\u002Fmodulejail\n```\n\nThis is the recommended path for any production deployment. The script is\nplain POSIX shell and inspection takes under ten minutes.\n\n## Native packages (.deb \u002F .rpm)\n\nFor Debian\u002FUbuntu and RHEL\u002FFedora\u002FRocky hosts, prebuilt packages are attached\nto the GitHub release page:\n\n```sh\n# Debian \u002F Ubuntu:\ncurl -fsSLO https:\u002F\u002Fgithub.com\u002Fjnuyens\u002Fmodulejail\u002Freleases\u002Fdownload\u002Fv1.2.3\u002Fmodulejail_1.2.3_all.deb\nsudo dpkg -i modulejail_1.2.3_all.deb\n\n# RHEL \u002F Fedora \u002F Rocky:\ncurl -fsSLO https:\u002F\u002Fgithub.com\u002Fjnuyens\u002Fmodulejail\u002Freleases\u002Fdownload\u002Fv1.2.3\u002Fmodulejail-1.2.3-1.noarch.rpm\nsudo rpm -i modulejail-1.2.3-1.noarch.rpm\n```\n\nBoth packages install `\u002Fusr\u002Fbin\u002Fmodulejail`, the `modulejail(8)` manpage\nunder `\u002Fusr\u002Fshare\u002Fman\u002Fman8\u002F`, and the README and LICENSE under\n`\u002Fusr\u002Fshare\u002Fdoc\u002Fmodulejail\u002F`. They depend on `coreutils`, `findutils`, and\n`awk`\u002F`gawk` (all standard) and recommend `curl` or `wget` so the optional\npost-run update check can reach GitHub.\n\nAfter install, `man 8 modulejail` shows the full reference: options,\nprofiles, safety model, idempotency, exit codes, environment, and examples.\n\nTo rebuild the packages locally from a checkout:\n\n```sh\n.\u002Fpackaging\u002Fbuild.sh           # builds whatever this host's tooling supports\n.\u002Fpackaging\u002Fbuild.sh --deb     # .deb only (requires dpkg-deb)\n.\u002Fpackaging\u002Fbuild.sh --rpm     # .rpm only (requires rpmbuild)\n```\n\nOutput goes to `packaging\u002Fdist\u002F`. The script skips gracefully on hosts\nwithout the matching tooling.\n\n## What ModuleJail is\n\nModuleJail snapshots the set of currently loaded modules (`\u002Fproc\u002Fmodules`) and\ncomputes the complement against the full module tree\n(`\u002Flib\u002Fmodules\u002F$(uname -r)`). Every module in the complement, minus a built-in\nbaseline of essential modules and an optional sysadmin-supplied whitelist, is\nemitted as an `install \u003Cmod>` directive in a `modprobe.d`-compatible\nblacklist file. Since v1.2, the directive body is either\n`\u002Fbin\u002Fsh -c 'logger -t modulejail \"blocked: \u003Cmod>\" ...; exit 0'` (default\nwhen `\u002Fusr\u002Fbin\u002Flogger` is available, so blocked attempts produce a syslog\ntrail) or `\u002Fbin\u002Ftrue` (under `--no-syslog-logging`, silent fallback, or when\nlogger is absent). See the *Viewing blocked module attempts* section below.\n\nThe tool is aimed at Linux fleet operators who need to harden many servers\nagainst the wave of AI-assisted kernel privilege-escalation discoveries. Every\nadditional loaded module is additional latent attack surface for the next\ndisclosed CVE. ModuleJail's model is simple: if it is not loaded today on a\nsteady-state host, blacklist it.\n\nThe script is portable across Debian\u002FUbuntu, RHEL\u002FRocky, Arch, Alpine, and\nSUSE families. It has no runtime dependencies beyond `awk`, `comm`, `find`,\n`sha256sum`, and standard coreutils, all present in every base Linux install\nincluding busybox.\n\n## The safety model\n\nThe invariant is: **whatever is currently loaded is assumed necessary for the\nhost to function, and is preserved.** ModuleJail does not guess; it reads\n`\u002Fproc\u002Fmodules` at run time and treats that exact set as the keep-list.\n\nThis means the operator's responsibility is to run ModuleJail when the host\nis in a known-good, steady-state configuration: after all services are\nstarted, all kernel drivers are loaded, all filesystems are mounted. Running\nit on a partial or in-flux system risks blacklisting a module that is\noccasionally needed.\n\nThe generated file is placed under `\u002Fetc\u002Fmodprobe.d\u002F`. To revert, remove the\nfile (no reboot needed — see the Reverting section). The built-in baseline\nensures that core filesystems, storage controllers, and essential networking\nmodules are never blacklisted regardless of the running profile.\n\n## Explicit limitations\n\n- **No initramfs handling.** Modules baked into initramfs are out of scope.\n  The loaded-module surface is the target; baked-in modules are not the\n  relevant attack vector.\n- **No revert tooling.** The revert path is \"remove the generated file\"\n  (no reboot needed; the blacklist is consulted by `modprobe` at load\n  time, so removing the file takes effect immediately). Sysadmin\n  discipline replaces tool guardrails.\n- **No daemon or continuous monitoring.** One-shot script by design.\n- **No AI inside the tool.** AI is the threat-model backdrop, not a feature.\n- **No per-distro packaging in v1.** The curl one-liner and a cloned repo\n  are the distribution channels.\n- **No module risk scoring.** The model is \"unused implies blacklist,\" not\n  \"vulnerable implies blacklist.\"\n- **No kernel rebuild.** Runtime blacklist only.\n\n## Profiles\n\nModuleJail ships three built-in baseline profiles. The selected profile\ndetermines which modules are always preserved regardless of loaded state.\n\n```sh\n# Profile selection via -p (default: conservative)\nsudo sh modulejail -p conservative\nsudo sh modulejail -p minimal\nsudo sh modulejail -p desktop\n```\n\nProfile descriptions (from `--help`):\n\n```\n  minimal       Core filesystems + essential kernel modules only\n  conservative  Minimal + common server\u002FVM drivers (default)\n  desktop       Conservative + WiFi, Bluetooth, audio, video drivers\n```\n\n`conservative` is the right choice for virtualised or bare-metal server\nLinux. `desktop` is for laptops and workstations where WiFi, Bluetooth,\naudio, and video drivers must be preserved. `minimal` is for environments\nwhere you have full control over which drivers are loaded and want the\nsmallest possible baseline.\n\n## The sysadmin whitelist\n\nA site-local `WHITELIST` variable near the top of the script holds\nspace-separated module names that are always preserved, beyond the selected\nbaseline. It ships empty.\n\nTo use it, open the script and find the `=== SYSADMIN WHITELIST ===` section:\n\n```sh\n# === SYSADMIN WHITELIST ===\n# Site-local additions to the keep-set, in addition to the selected baseline\n# profile. Modules listed here will never appear in the generated blacklist.\n#\n# Format: space-separated module names in canonical underscore form\n#         (the pipeline normalizes - to _, so either form works).\n# Default: empty.\n#\n# Example (uncomment and adapt):\n# WHITELIST='nft_compat xt_owner'\nWHITELIST=''\n# === END SYSADMIN WHITELIST ===\n```\n\nEdit `WHITELIST=''` to add your site-specific modules. The `===` banner\nanchors are designed for Ansible template insertion (`lineinfile` or\n`blockinfile`).\n\n## Site-local whitelist file\n\nSince v1.2, ModuleJail reads site-local modules from an external file.\nThis is the preferred path when you do not want to (or cannot) edit the\nscript in place — for instance because you install ModuleJail via\n`.deb` \u002F `.rpm` \u002F `curl | sh` and your site-local additions would\notherwise be lost on the next reinstall.\n\nThe default path is `\u002Fetc\u002Fmodulejail\u002Fwhitelist.conf`. If the file\nexists, ModuleJail auto-detects it and prints an `info:` line on\nstderr so the choice is not silent:\n\n```\nmodulejail: info: using default whitelist file \u002Fetc\u002Fmodulejail\u002Fwhitelist.conf (--no-whitelist-file to opt out)\n```\n\nTo skip the default for a single run (e.g. during recovery), pass\n`--no-whitelist-file`. To use a different location, pass\n`--whitelist-file PATH`.\n\nFile format:\n\n```sh\n# \u002Fetc\u002Fmodulejail\u002Fwhitelist.conf\n# One module per line. Blank lines and '#' comments are allowed.\n# Names may be written in either dash or underscore form (\"nft-compat\"\n# or \"nft_compat\") — the pipeline normalises - to _.\n# The file mode MUST NOT be group-writable or world-writable\n# (ModuleJail will refuse to run otherwise).\n\nnft_compat\nxt_owner\nzfs\n```\n\nThree ways to invoke:\n\n```sh\n# 1. Default location (recommended for production deploys):\nsudo install -d -m 0755 \u002Fetc\u002Fmodulejail\nsudo install -m 0644 my-whitelist \u002Fetc\u002Fmodulejail\u002Fwhitelist.conf\nsudo modulejail   # auto-detects \u002Fetc\u002Fmodulejail\u002Fwhitelist.conf\n\n# 2. Explicit non-default path (override or use a site-local NFS mount):\nsudo modulejail --whitelist-file \u002Fetc\u002Fdefault\u002Fmodulejail-whitelist\n\n# 3. Skip the default for one run (force \"no site-local additions\"):\nsudo modulejail --no-whitelist-file\n```\n\nThe file is appended to the in-script `WHITELIST`; the two are additive.\nOperators who have been editing the in-script `WHITELIST` (the v1.0\npath) keep that edit untouched; the file is a no-side-effect overlay on\ntop.\n\nModuleJail enforces two safety gates on the file:\n\n1. **File mode must not be group- or world-writable.** The same\n   hardening sshd applies to `authorized_keys` and sudo applies to\n   `sudoers`. If the file is `g+w` or `o+w`, ModuleJail refuses to run\n   and prints `chmod go-w PATH` as the hint. Exit code is `77`\n   (`EX_NOPERM`). Rationale: the module names from this file land in\n   the generated `modprobe.d` directives, so an attacker with write\n   access to a shared sysadmin group could otherwise inject `install`\n   stanzas the kernel would later run.\n2. **Each line must match `[a-zA-Z0-9_-]+`.** Comments (`#`) and blank\n   lines are skipped silently; everything else must be a plain module\n   name. Any malformed line is rejected with a stderr message citing\n   the file path, line number, and offending content. Exit code is\n   `65` (`EX_DATAERR`).\n\n## Viewing blocked module attempts\n\nSince v1.2, when `\u002Fusr\u002Fbin\u002Flogger` is executable on the host running\nModuleJail (and `--no-syslog-logging` is not set), the generated\ninstall lines call `logger -t modulejail \"blocked: \u003Cmodule>\"` so a\nlater `modprobe \u003Cmodule>` attempt produces a syslog entry tagged\n`modulejail`:\n\n```sh\n# systemd hosts (journald):\nsudo journalctl -t modulejail --since '1 hour ago'\n\n# classic syslog hosts:\nsudo grep modulejail \u002Fvar\u002Flog\u002Fsyslog\n```\n\nThe generated file's header annotates which install-line form is in\nuse. Look for:\n\n```\n# install-line: \u002Fbin\u002Fsh + logger (syslog tag: modulejail)\n```\n\nTo opt out and restore the exact v1.1.4 `\u002Fbin\u002Ftrue` install-line body\n(useful for byte-identical regression contracts, hosts without\n`logger`, or minimal\u002Finitramfs builds), pass `--no-syslog-logging`:\n\n```sh\nsudo modulejail --no-syslog-logging\n```\n\nThe header annotation then reads:\n\n```\n# install-line: \u002Fbin\u002Ftrue (silent, --no-syslog-logging or logger absent)\n```\n\nIf `\u002Fusr\u002Fbin\u002Flogger` is absent on the host AND `--no-syslog-logging`\nwas not set, ModuleJail silently falls back to the `\u002Fbin\u002Ftrue` form\n(matching the v1.1.4 behaviour on minimal hosts). No stderr warning is\nemitted; the header annotation is the only visible cue.\n\n## Scope of the blacklist (what it blocks, what it doesn't)\n\nA `modprobe.d` blacklist blocks **automatic** module loading: udev\nevents on hardware hotplug, dependency resolution during\n`modprobe foo`, autoloaded modules through the alias system. It does\n**not** block, by design:\n\n- `insmod \u002Fpath\u002Fto\u002Fmodule.ko` — `insmod` bypasses `modprobe` entirely\n  and never reads `modprobe.d\u002F`. A root user with intent can always\n  insert a module directly.\n- `modprobe --ignore-install \u003Cname>` — `modprobe`'s explicit escape\n  hatch. The user is opting out of the install-line indirection that\n  ModuleJail relies on.\n\nBoth are intentional escape hatches in the kernel module loader.\nModuleJail is a default-safe policy layer: it removes the\nauto-loading attack surface (udev hotplug + dependency resolution),\nwhich is what an unprivileged or remote attacker has to work with. It\ndoes not — and could not — prevent a root user with intent from\nloading anything they want. Treat the blacklist as the \"lock the\nfront door\" tool, not as the \"lock the safe\" tool.\n\n## Exit codes\n\nExit codes follow `sysexits.h` conventions (see `man 3 sysexits`). Fleet\nautomation tools can `case $?` cleanly.\n\n| Code | Meaning |\n|------|---------|\n| 0    | success |\n| 64   | command-line argument error (bad flag, missing value, unknown profile) |\n| 65   | invalid data in `--whitelist-file` (malformed module name) |\n| 66   | required kernel input missing (`\u002Fproc\u002Fmodules` or `\u002Flib\u002Fmodules\u002F\u003Ckernel>`) |\n| 70   | sanity guard tripped (empty blacklist or >99% of modules blacklisted) |\n| 71   | OS-level error (mktemp work dir, or find errors on `\u002Flib\u002Fmodules`) |\n| 73   | output path cannot be created (symlink\u002Fdirectory\u002Ftrailing-slash, or mktemp failure) |\n| 77   | target directory not writable (try sudo, or use `-o \u003Cother-path>`) |\n\n## Idempotency contract\n\nTwo consecutive runs on an unchanged host produce byte-identical output\nfiles. The generated blacklist header carries a sha256 run fingerprint, not\na wall-clock timestamp, computed over the canonical inputs: sorted\nloaded-module set, sorted baseline set, sorted whitelist, profile name, and\nkernel version. Because the fingerprint is a deterministic function of\ninputs, identical inputs produce an identical fingerprint and thus an\nidentical output file.\n\n```\n# fingerprint: sha256:e284ee9741eb544adf1af6c0fffc162dedd6029191673237a8155cd497908686\n```\n\nFleet operators can use the fingerprint to correlate \"what was on the host\nat hardening time\" across machines: two hosts with the same fingerprint had\nidentical loaded sets, baseline, whitelist, profile, and kernel version when\nModuleJail ran. No wall-clock drift, no spurious diffs in configuration\nmanagement systems.\n\n## Update check\n\nAfter a successful run, ModuleJail performs a best-effort lookup against the\nGitHub tags API to see whether a newer release is available. The check has a\n10-second hard timeout and is silent on every failure mode (no network, no\n`curl` or `wget` installed, parse failure, current version equal to or newer\nthan the latest tag). It only prints a stderr notice when the upstream\nrelease is strictly newer than the running version.\n\nTo disable the check entirely (for offline fleets, restricted networks, or\npipeline-style automation where any unexpected output is noise), set:\n\n```sh\nexport MODULEJAIL_NO_UPDATE_CHECK=1\n```\n\nThe check fires only on a successful run. Error paths (bad arguments,\nmissing `\u002Fproc\u002Fmodules`, sanity-guard trip, etc.) exit before reaching it.\n\n## Cross-distro support\n\nModuleJail has been verified across two confidence tiers.\n\n### Real-kernel tier (live SSH hosts)\n\n| Distro | Kernel | Result |\n|--------|--------|--------|\n| Ubuntu 24.04.4 LTS (Noble Numbat) | 6.8.0-110-generic | PASS (6363 of 6474 modules blacklisted) |\n| Debian GNU\u002FLinux 13.4 (trixie) | 6.12.74+deb13+1-amd64 | PASS (4091 of 4227 modules blacklisted) |\n| Rocky Linux 9.7 (Blue Onyx) | 5.14.0-503.35.1.el9_5.x86_64 | PASS (2253 of 2338 modules blacklisted) |\n\nNote for Rocky\u002FRHEL hosts: on hosts with strict SELinux enforcement,\nnon-root execution may encounter a `find` permission denial on\n`\u002Flib\u002Fmodules\u002F`, causing exit code 71 (`EX_OSERR`). This is expected,\ndocumented behaviour. Use `sudo`, or relax the relevant SELinux policy, if\nthis occurs.\n\n### Fixture-container tier (synthetic kernel module trees)\n\n| Distro | Base image | Shell | Result |\n|--------|-----------|-------|--------|\n| Arch Linux (latest) | `archlinux:latest` | `\u002Fbin\u002Fsh` (bash) | PASS (10\u002F10 assertions) |\n| Alpine Linux (latest) | `alpine:latest` | busybox ash | PASS (10\u002F10 assertions) |\n| openSUSE Tumbleweed | `opensuse\u002Ftumbleweed:latest` | `\u002Fbin\u002Fsh` | PASS (10\u002F10 assertions) |\n\nFixture containers run against a synthetic\n`\u002Flib\u002Fmodules\u002F6.99.0-fixture\u002F` tree with representative `.ko`, `.ko.gz`,\n`.ko.xz`, and `.ko.zst` files to exercise all four suffix variants.\n\nThe `MODULEJAIL_PROC_MODULES` and `MODULEJAIL_KVER` environment variables\nare test-only plumbing (analogous to `TMPDIR` or `GIT_DIR`) used by the\nfixture harness to point the script at synthetic `\u002Fproc\u002Fmodules` and module\ntree paths. End-user operators leave these unset.\n\n## Reverting\n\nRemove the generated file. The blacklist is consulted by `modprobe` at\nload time, not loaded into the kernel persistently, so removing the file\ntakes effect immediately — no reboot needed.\n\n```sh\n# Full revert (instant, no reboot needed):\nsudo rm \u002Fetc\u002Fmodprobe.d\u002Fmodulejail-blacklist.conf\n\n# Selective: bring back a specific module right now, even while the\n# blacklist file is still in place (`modprobe` is the explicit-load\n# path that overrides the blacklist):\nsudo modprobe \u003Cmodule_name>\n```\n\nThe generated file uses `install \u003Cmodule> ...` directives (with either a\n`\u002Fbin\u002Fsh + logger` body or `\u002Fbin\u002Ftrue`, see *Viewing blocked module\nattempts* above), which block autoloading via udev events and dependency\nresolution. Explicit `sudo modprobe \u003Cname>` invocations override the\nblacklist immediately, regardless of whether the file is still present.\nIf the file is still in place, the override applies only to that single\nexplicit load — subsequent autoload attempts (from udev or other modules\nrequiring the named module as a dependency) will be blocked again. To\nmake the unblock permanent, remove the blacklist file. See *Scope of the\nblacklist* above for the precise list of what `modprobe.d` install\ndirectives do and do not intercept.\n\n## Contributing\n\nThe test matrix lives in `tests\u002F`. Both harnesses are POSIX shell scripts\nrunnable by anyone with the prerequisites:\n\n```sh\n# Container fixture suite (Arch\u002FAlpine\u002FopenSUSE):\n# Requires: docker or podman; exits 77 if neither is found (graceful skip).\n.\u002Ftests\u002Frun-fixtures.sh\n\n# Real-SSH-host acceptance suite:\n# Requires: SSH key access to the hosts configured in the harness.\n.\u002Ftests\u002Frun-ssh-hosts.sh\n```\n\n`.\u002Ftests\u002Frun-fixtures.sh` exits 77 on any host without a container runtime;\nthat is the documented graceful degradation (autoconf\u002FTAP skip convention).\nRun it on a Linux host with Docker or Podman.\n\nBoth harnesses are shellcheck-clean (`shellcheck --shell=sh`).\n\n## License\n\nCopyright (C) 2026 Jasper Nuyens \u003Cjnuyens@linuxbe.com>\n\nGPL-3.0-only. See the [LICENSE](LICENSE) file for the full text.\n","ModuleJail 是一个用于减少 Linux 主机内核模块攻击面的工具，通过黑名单机制禁止所有当前未使用的内核模块。它采用单一 POSIX Shell 脚本实现，无需守护进程、initramfs 修改或人工智能支持，运行一次即可生成相应的黑名单文件。核心功能在于自动生成并应用针对特定系统的内核模块黑名单，同时允许系统管理员根据需要设置白名单。适用于希望降低潜在安全风险、提高系统安全性的服务器和工作站环境，特别是在面临大规模内核模块漏洞披露时，能够为运维人员争取更多响应时间。","2026-06-11 03:55:23","CREATED_QUERY"]