# kernel-hardening-checker: Kconfig, command line and sysctl, with a TODO for performance

> This is the tool the kernel hardening community uses to diff a running system against recommended settings, and its design is instructive: three layers of configuration, five architectures, seven independent sources of recommendations, and a hardcoded humility in the FAQ, where the performance question is answered by pointing at an open issue. The packaging is unusual too, three forges and two CI systems for a console script that fits in one file.

**a13xp0p0v/kernel-hardening-checker** — A tool for checking the security hardening options of the Linux kernel

- Repository: https://github.com/a13xp0p0v/kernel-hardening-checker
- Stars: 2,140 · Forks: 193
- Language: Python
- License: GPL-3.0
- Published: 2026-09-29 · Updated: 2026-09-29 · Language: en
- Canonical page: https://hysenlabs.com/projects/a13xp0p0v-kernel-hardening-checker

## Three layers of configuration, five architectures, seven sources

The tool checks three different things, and the split is by when a setting takes effect: Kconfig options are compile-time, kernel command line arguments are boot-time, and sysctl parameters are runtime. Each has its own flag, -c for a config file, -l for a command line, -s for sysctls, plus -a to autodetect the running system, -p to select an architecture and -v for a kernel version.

Architectures covered are X86_64, X86_32, ARM64, ARM and RISC-V.

What makes the output arguable is the recommendation list, which is not one opinion. It is built from KSPP recommended settings, direct feedback from Linux kernel maintainers, kernel options that grsecurity disables in order to cut attack surface, the CLIP OS kernel configuration, GrapheneOS recommendations, the SECURITY_LOCKDOWN_LSM patchset, and the CIS Benchmark.

That is seven sources with different goals, and the tool does not hide it: verbose mode prints the internals of a compound check, including which source each part came from. In the example given, a check satisfied by one of two options shows the option names next to a defconfig label and a kspp label, so you can see whether a setting passed because of a distribution default or because someone turned it on deliberately.

The same author also draws a Linux Kernel Defence Map, a diagram of how hardening features relate to vulnerability classes and exploitation techniques.

## Autodetect saves sysctls to a temp file and misses the privileged ones

The autodetect run prints what it found and what it could not read:

```
[+] Detected version of the running kernel: (6, 11, 0)
[+] Detected kconfig file of the running kernel: /boot/config-6.11.0-1007-oem
[+] Detected cmdline parameters of the running kernel: /proc/cmdline
[+] Saved sysctls to a temporary file /tmp/sysctl-at_0n9si
[+] Detected architecture: X86_64
[+] Detected compiler: GCC 130200
[!] WARNING: sysctl options available for root are not found in /tmp/sysctl-at_0n9si, try checking the output of "sudo sysctl -a"
```

The sysctl layer is collected by running sysctl and writing the output into a temporary file under /tmp, and an unprivileged run cannot read the parameters that only root can see. The tool detects that gap and prints a warning naming the fix, sudo sysctl -a.

That is a well-behaved failure, and it is still a failure mode. A CI job that runs this as an ordinary user gets a report with an unknown number of missing sysctls, and the warning is one line among six, so a log-scraping pipeline has to notice it.

Detecting the compiler version is the other detail. GCC 130200 is read from the running build, which matters because some hardening options are only available or only meaningful depending on what compiled the kernel.

## The performance question is answered with an open issue

The README carries a section of questions and answers, and the second question is the one every adopter asks: what about the performance impact of these hardening features?

The answer refuses to generalise. It says this is not an easy question because the impact depends on the workload, and then it says that a detailed evaluation of the performance impact of Linux security hardening features is in TODO, with the issue number given. Two pieces of prior work are offered as reading instead, one a set of performance tests described in an article, the other a paper by four authors on systems security.

So the project is explicit that the trade-off curve is workload-dependent and that its own answer to it is unfinished.

The attention note above the installation section says the same thing in a different register: changing the kernel security parameters may also affect the performance and the functionality of userspace software, so consider the threat model of your system and test its typical workload thoroughly.

Between those two, the tool is best read as an auditor of configuration rather than a tuner of performance. It answers whether the settings match, and it declines to answer what that will cost you.

## Three forges, two CI systems, four test workflows

The project is mirrored three times, and the README says why. GitHub is the primary, Codeberg is offered as the place to go if something goes wrong with GitHub, and SourceCraft is the third. The badge row points at a Codeberg CI project as well as at GitHub Actions.

The tree carries both CI systems. A .github directory holds the Actions workflows, and the badges name four of them: a functional test, an engine unit test, static analysis and a package test. A .woodpecker directory sits alongside, which is a different CI engine entirely.

Code coverage is reported per workflow rather than as one number, and the badges are parameterised by flag, with a separate Codecov view for the functional test and another for the engine unit test.

For a console tool, this is a lot of machinery, and the reason is visible in the project structure: the checker is a Python package with a man page, a bin wrapper and a distribution manifest, so it has to survive being packaged into distributions, tested as a package, and built on more than one forge. The mirrors are also a resilience choice, which for a tool whose users are distro maintainers is a reasonable one.

## Tags with no releases, and a version read from the package attribute

The badge row links to the repository's tags page, and the release list is empty. There are no GitHub releases, which is consistent with how the project is consumed: distributions package it, and the README points at a version listing on repology as the way to see what your distribution ships.

So the version number is a tag rather than a release artifact, and nothing in the repository records checksums or release notes.

The packaging metadata makes the same choice explicit. The project declares its version as dynamic, taken from an attribute on the package itself:

```toml
[tool.setuptools.dynamic]
version = {attr = "kernel_hardening_checker.__version__"}
```

There is exactly one console script, mapping the command name to the main function in the package, and one licence declaration using the SPDX identifier form for GPL version 3 only, with the licence file named alongside it. The declared Python floor is 3.9, which is older than the packaging standard it relies on, since the build system requires setuptools 77.0.3 or newer for the licence expression it uses.

The classifiers are short and specific: production or stable status, a security topic, a Linux operating system, a console environment.

## The lint list selects the same check twice

The lint configuration is the most opinionated part of the repository, and it is worth reading as documentation of the project's own standards.

Ruff is selected with a long list of rule families, each annotated with the flake8 plugin it comes from. The security family is there, and so is the bugbear family, builtins, comprehensions, comprehensions again under a different name, the copyright family, the return family, self, simplify, unused arguments, pie, quotes, implicit string concatenation, gettext, and a fixme family that exists to keep TODO comments visible.

Two of those entries are the same check. The list selects both the EXE family and the ISC family, and both are annotated as flake8 implicit string concatenation, because the rule moved between plugin prefixes and both prefixes are still available. Selecting both means each such finding is reported twice, once under each name.

One entry carries an instruction rather than a plugin name: the simplify family is annotated with a request to fix its issues manually rather than automatically, which is a hint about the kind of code changes this project wants reviewed by a person.

Pylint is configured alongside it with a line length of 120 and a raised statement limit of 100, which is a permissive setting for a tool that mostly parses text and prints a table.

## Verbose mode prints the internals of a compound check

The output modes are what make the tool scriptable, and one of them is unusually revealing.

The default mode gives the pass and fail table. Verbose mode adds two things: the configuration options that have no corresponding check at all, and the internals of checks built from AND and OR. That second one looks like this:

```
    <<< OR >>>
CONFIG_STRICT_DEVMEM                  |kconfig|cut_attack_surface|defconfig |     y
CONFIG_DEVMEM                         |kconfig|cut_attack_surface|   kspp   | is not set
```

So a check that passes if either option is set prints both options with their current values and the label of the source that recommends each. The two source labels visible here are cut attack surface and kspp, which is the recommendation provenance carried all the way into the output.

The other three modes are for pipelines. JSON output is there for combining the tool with other tools, and show_ok and show_fail narrow the table to the checks that passed or the checks that did not.

The generation mode is the mirror image of the check mode. With -g the tool writes a Kconfig fragment for the selected architecture, which is then merged into a kernel source tree with the kernel's own merge_config.sh, and that script prints a warning when the fragment redefines a value that was already set.

## Conclusion

Use kernel-hardening-checker when you want a defensible, citable answer to whether a kernel matches the hardening baseline you claim, because the checks name their sources and the verbose mode shows which source each one came from, which is what makes the output usable in an audit. Do not expect it to tell you whether turning those options on is a good idea for your workload: the project says so itself, and its own performance evaluation is still an open issue. Before you adopt its output as a target, note that the unprivileged autodetect path cannot see root-only sysctls and reports them as missing, and that the recommendations draw on grsecurity and the CLIP OS configuration, so the baseline is opinionated rather than neutral.

## FAQ

### What does kernel-hardening-checker actually check?

Three layers: Kconfig options at compile time, kernel command line arguments at boot time, and sysctl parameters at runtime, across X86_64, X86_32, ARM64, ARM and RISC-V. Its recommendations come from KSPP settings, kernel maintainer feedback, grsecurity-disabled options, CLIP OS, GrapheneOS, the SECURITY_LOCKDOWN_LSM patchset and the CIS Benchmark.

### Why does kernel-hardening-checker warn about missing sysctls?

It collects sysctls by writing the output of sysctl to a temporary file, and an unprivileged run cannot read the parameters only root can see. The tool prints a warning saying the root-only options are not in that file and suggests checking the output of sudo sysctl -a.

### Does the README say anything about the performance cost of the hardening options?

It says the impact depends on the workload and cannot be answered generally, and that a detailed evaluation of the performance impact of Linux security hardening features is still to do, with the tracking issue linked. It also warns that changing these parameters can affect userspace software and should be tested against a typical workload.

### How do I install kernel-hardening-checker?

Install from the Git repository with pip, create a venv if you hit an externally managed environment error, install the distribution package where one exists, or run ./bin/kernel-hardening-checker from a clone with no installation step at all. A version listing on repology shows what distributions ship.

### How can I get a Kconfig fragment from kernel-hardening-checker?

Use the -g argument with an architecture, redirect the output to a file, and merge it into your kernel source tree with the kernel's own merge_config.sh. That script prints a warning when a value in the fragment redefines one already set in the existing config.

## Sources

- [a13xp0p0v/kernel-hardening-checker on GitHub](https://github.com/a13xp0p0v/kernel-hardening-checker)
- [Issues](https://github.com/a13xp0p0v/kernel-hardening-checker/issues)
- [License: GPL-3.0](https://github.com/a13xp0p0v/kernel-hardening-checker/blob/master/LICENSE)
- [README](https://github.com/a13xp0p0v/kernel-hardening-checker/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/a13xp0p0v-kernel-hardening-checker
