Beta. The family is days old and still changing. Package names, flags and keys may move without notice until 1.0. Pin versions, and report what breaks.
One screen for the security posture of one Linux machine. Eight probes — Secure
Boot, the MAC layer, the firewall, sshd, pending updates, accounts, kernel
hardening and listening ports — each answered ok, warn, bad or unknown,
with the exact command behind every verdict.
Checking any of this by hand means bootctl, getenforce, ufw, sshd -T,
checkupdates, sysctl and ss, in seven terminals, remembering what a good
answer looks like for each. tui-secure puts them on one screen, in the
Omarchy visual style, and shows its working.
Status: early, under validation. An independent tool that follows the Omarchy visual style; it is not part of the Omarchy project and not endorsed by its maintainers. Expect rough edges.
Needs the tui-tools repository, which is a one-time setup.
The one-liner detects the distribution and adds the repository and its signing key:
curl -fsSL https://pkgs.tui.tools/install.sh | shPiping a script into a shell is not this family's style, so here is the same
setup by hand — read it, or read the script first with curl -fsSL https://pkgs.tui.tools/install.sh -o install.sh:
curl -fsSL -o /tmp/tui-tools.asc https://pkgs.tui.tools/pubkey.asc
sudo pacman-key --add /tmp/tui-tools.asc
sudo pacman-key --lsign-key \
"$(gpg --show-keys --with-colons /tmp/tui-tools.asc | awk -F: '/^fpr:/{print $10; exit}')"
printf '[tui-tools]\nServer = https://pkgs.tui.tools/arch/$arch\n' \
| sudo tee -a /etc/pacman.conf
sudo pacman -SyThen, and for every other tool in the family:
sudo pacman -S tui-secureUpgrades then arrive with the rest of your system updates.
Needs the tui-tools repository, which is a one-time setup.
The one-liner detects the distribution and adds the repository and its signing key:
curl -fsSL https://pkgs.tui.tools/install.sh | shPiping a script into a shell is not this family's style, so here is the same
setup by hand — read it, or read the script first with curl -fsSL https://pkgs.tui.tools/install.sh -o install.sh:
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://pkgs.tui.tools/pubkey.asc \
| sudo gpg --dearmor -o /etc/apt/keyrings/tui-tools.gpg
echo "deb [signed-by=/etc/apt/keyrings/tui-tools.gpg] https://pkgs.tui.tools/deb stable main" \
| sudo tee /etc/apt/sources.list.d/tui-tools.list
sudo apt updateThen, and for every other tool in the family:
sudo apt install tui-secureUpgrades then arrive with the rest of your system updates.
Needs the tui-tools repository, which is a one-time setup.
The one-liner detects the distribution and adds the repository and its signing key:
curl -fsSL https://pkgs.tui.tools/install.sh | shPiping a script into a shell is not this family's style, so here is the same
setup by hand — read it, or read the script first with curl -fsSL https://pkgs.tui.tools/install.sh -o install.sh:
sudo rpm --import https://pkgs.tui.tools/pubkey.asc
sudo curl -fsSL -o /etc/yum.repos.d/tui-tools.repo https://pkgs.tui.tools/rpm/tui-tools.repo
sudo dnf makecacheThen, and for every other tool in the family:
sudo dnf install tui-secureUpgrades then arrive with the rest of your system updates.
curl -fsSL https://github.com/tui-tools/tui-secure/releases/download/v0.2.2/tui-secure_0.2.2_linux_amd64.tar.gz | tar -xz tui-secure
sudo install -m0755 tui-secure /usr/local/bin/tui-secureOne static binary. Verify it against checksums.txt from the same release.
git clone https://github.com/tui-tools/tui-secure
cd tui-secure && make build
sudo install -m0755 bin/tui-secure /usr/local/bin/tui-secureNeeds Go 1.27 or newer.
Not packaged for these yet; the static binary works everywhere in the meantime.
paru -S tui-secure-binThe -bin package installs the released static binary.
Needs the tui-tools repository, which is a one-time setup.
sudo zypper install tui-secureThe rpm repository is shared with dnf; zypper support is not tested yet.
Every release of tui-secure ships a checksums.txt. Check an archive against
it before installing:
sha256sum -c checksums.txt --ignore-missingWebsite: https://tui.tools/tools/tui-secure/
One static binary, no daemon, no state of its own. Nothing keeps running after you quit.
tui-secure --demo--demo runs against a sample machine: Secure Boot on, SELinux enforcing with
a few denials, ufw active, an sshd that still takes passwords, a dozen pending
updates with a reboot owed, a second account with UID 0, and one kernel
hardening key left at zero. Every key works, every command is built and
previewed for real, and nothing touches your system.
enter opens a probe: the settings it judged, why each one is worth what it
is worth, the exact command it ran, the line it based the verdict on, and the
full raw output underneath. A verdict you cannot check is a verdict you have to
trust, and this tool would rather be argued with.
| Probe | What it reads | Verdict |
|---|---|---|
| Secure Boot | bootctl status, and sbctl status where sbctl is installed |
ok enabled · warn off · bad firmware in setup mode · unknown no EFI |
| Access control | getenforce and sestatus, or aa-status --json; denials from journalctl -k, or ausearch -m avc where auditd runs |
ok enforcing · warn permissive, complain-only, disabled, or no MAC layer at all |
| Firewall | ufw status verbose, or firewall-cmd --state and its default zone, or the nft ruleset and the file nftables.service would load |
ok active · warn a permissive default · bad installed and off, or nothing at all |
| SSH server | sshd -T, falling back to sshd_config and its drop-ins; systemctl is-active; failed logins from the journal |
bad root login with a password, empty passwords permitted · warn root login with a key, passwords on, keys off, MaxAuthTries above 4, X11 forwarding on · unknown a keyword sshd did not report |
| Updates | checkupdates / pacman -Qu, apt-get -s upgrade, dnf check-update; whether a reboot is owed; the unattended update timer |
ok nothing pending · warn updates waiting, a reboot owed, or no timer enabled |
| Accounts | /etc/passwd for UID 0, /etc/shadow for empty passwords, sudo -n -l for NOPASSWD |
bad a second root or an empty password · warn passwordless sudo · unknown /etc/shadow needs root |
| Kernel hardening | sysctl -n for kernel.kptr_restrict, kernel.dmesg_restrict, fs.protected_*, fs.suid_dumpable, net.ipv4.ip_forward and kernel.core_pattern |
ok the basics are set · warn a key below the recommended value; net.ipv4.ip_forward is reported in a clause of its own and never counted as a fix, so the number in the line is the number of fixes behind a |
| Listening ports | ss -tulpnH, with the process behind each socket and the unit it belongs to, read from /proc/<pid>/cgroup |
ok everything on loopback · warn sockets reachable from the network |
The score in the header is the weighted count: an ok counts whole, a warn
counts half, a bad counts nothing. An unknown is counted and not weighed —
scoring a question nobody could answer would be inventing a verdict.
tui-secure measures, and it fixes the findings whose right answer is the same
on every machine. Everything else names its owner: the whole of sshd_config
is tui-ssh's, accounts are tui-users's, rule-by-rule firewall work is
tui-firewall's, applying updates is tui-update's. Where no tool owns it yet,
the probe shows the command to run by hand.
What it will run itself are the one-liners it can preview in full, and each is shown as an exact command line and confirmed first:
| Key | What runs |
|---|---|
a on the firewall probe |
ufw enable, or systemctl enable --now firewalld, or systemctl enable --now nftables — whichever firewall this machine actually has |
a on the SSH probe |
sshd -t -f <staged drop-in>, then install -m 600 <it> /etc/ssh/sshd_config.d/50-tui-secure.conf, then systemctl reload sshd. One keyword at a time, and every keyword the probe grades as a weakness has its own Set X to Y: PermitRootLogin no, PasswordAuthentication no, PermitEmptyPasswords no, PubkeyAuthentication yes, MaxAuthTries 4, X11Forwarding no |
a on the kernel probe |
install -m 644 <staged drop-in> /etc/sysctl.d/90-tui-secure.conf, then sysctl -w <key>=<value> |
a on the updates probe |
systemctl enable --now <the distribution's update timer> |
a on the ports probe |
systemctl disable --now <the unit behind the port> |
y runs the commands shown, n does not. There is no other path to a change:
the UI hands the same values to the preview and to the runner, so what you read
is what executes. Both drop-ins are written to a private temporary directory
first, shown in full, and only the install you approved puts one in /etc.
A fix that can lock you out of your own machine is worse than no fix, so some changes are refused rather than warned about:
- Turning off password authentication with no key anywhere. Before the sshd
plan is built, the accounts in
/etc/passwdare walked for anauthorized_keyswith something in it. If none has one — or if the only one that does isrootandPermitRootLoginis going tonoat the same time — the change is refused, and the message says why. A home directory that could not be read is not an account without a key: those are listed on the dialog instead, because inventing the answer in either direction would be worse than naming what could not be looked into. - Turning off both authentication methods.
PasswordAuthentication nowithPubkeyAuthentication nois a server nothing can log in to at all. - Enabling
nftables.servicewith no ruleset to load. The unit is a loader for/etc/nftables.conf(or/etc/sysconfig/nftables.conf). Without that file, or with onenft -c -fwill not parse, the action is refused: a service that comes up and filters nothing looks like a fix and is not one.
A change that would be read and ignored is warned about rather than
refused: sshd keeps the first value it is given for a keyword, so a drop-in
sorting before 50-tui-secure.conf — Fedora's 50-redhat.conf, for one —
wins. The dialog names that file and says to change it there instead.
The ssh server is also never offered as something to stop, whatever port the ports probe finds it on.
Most probes read unprivileged. Some cannot: ufw status refuses a plain user,
sshd -T needs root, /etc/shadow is root-only, and so is the nftables
ruleset. Those go through sudo -n, which never prompts — so a machine
where you have not run sudo -v gets unknown with the reason, and never a
screen blocked on a password you did not expect.
That is why unknown is a first-class verdict here rather than a failure. It
means nobody answered, and the probe says who did not.
tui-secure # probe this machine
tui-secure --demo # sample machine, no privileges needed
tui-secure --check # run every probe, print JSON, exit
tui-secure --report # print what a bug report needs, exit
tui-secure --theme ~/mytheme/colors.toml
tui-secure --sudo "" # run the commands directly (as root)
tui-secure --version--check is the non-interactive path: it runs every probe through the same
backend the UI uses, prints the result as JSON and exits. No UI, and it never
builds or runs a fix, so it is safe to run anywhere.
$ tui-secure --check | head -14
{
"tool": "tui-secure",
"version": "0.1.0",
"backend": "host",
"describe": "this machine, escalating with sudo -n",
"distro": "Fedora Linux 42 (Workstation Edition)",
"distroId": "fedora",
"kernel": "6.19.14-108.fc42.x86_64",
"stack": {
"secureBoot": "SB: on",
"mac": "MAC: SELinux disabled",
"firewall": "firewall: firewalld",
"sshd": "sshd: running",
"updates": "updates: fedora"The exit code is not the verdict. --check exits 0 whenever the tool
worked, however bad the news is, and puts the verdict in worst:
tui-secure --check | jq -r .worst # ok | warn | bad | unknownA machine with no firewall and root logins enabled is a successful run of
tui-secure. Conflating that with "the tool is broken" would make the exit
code useless for both, so a script reads the field.
tui-lab uses --check to test this
tool against real machines on Ubuntu, Fedora and Omarchy Server; the assertions
live in test/smoke.sh.
--report prints, in one block, everything a maintainer has to ask for
otherwise: the tool and kit versions, the version of every program this tool
probes and which of them the machine does not have, the distribution, the
kernel, the terminal, the theme, the escalation prefix, and whether the running
binary came from a package. It needs no privileges and runs no probe, so it
works on the machine where the bug is — including one where the tool cannot
even build its backend, which is itself worth reporting.
$ tui-secure --report
tui-secure 0.1.0 (kit v0.2.9)
backend: host (version unknown: the machine itself, so there is no one version to read)
mode: live
distro: fedora 42 (Fedora Linux 42 (Workstation Edition))
kernel: 6.19.14-108.fc42.x86_64
arch: x86_64
locale: en_US.UTF-8
term: xterm-256color
theme: tokyo-night
sudo: sudo -n
root: no
binary: /usr/bin/tui-secure (packaged)
backends: systemd 257, openssh 9.9, ufw absent, firewalld 2.3.2, sbctl absentThe backend is the machine itself, so it has no version of its own; the
programs behind the probes each have one, and the backends line carries them
all — including the ones that are absent, because a probe that says nothing
about ufw on a machine without ufw is right, and on a machine with it is a bug.
The block is written to be published as it is: it carries no hostname, user
name, home path or address, and no environment variable beyond LANG,
LC_ALL, TERM and TERM_PROGRAM. A binary living under your home directory
is reported as being there without naming the path. --report works with
--demo too, where it says so on the mode line.
The bug form asks for this block first — see
.github/ISSUE_TEMPLATE/bug_report.yml.
| Key | Action |
|---|---|
↑/k, ↓/j |
Move the selection, or scroll the detail screen |
g / G |
First / last probe |
pgup / pgdn |
Scroll a page |
enter |
Open the probe: evidence, raw output and the fix |
esc |
Leave the detail screen |
/ |
Filter the probes (matches name, verdict, summary and findings; esc clears) |
a |
Apply an offered fix, previewed and confirmed first |
r |
Re-run every probe |
R |
Re-run the selected probe |
? |
Help |
q |
Quit |
- Run eight probes against the real machine, each with its own command, its own grading rules and its own fix.
- Show, for every verdict: the settings it was assembled from, the command that read them, the line it judged, and the full raw output.
- Degrade to
unknownwith the reason whenever a binary is missing or a read needs a root this process was not given — never a crash, never a password prompt. - Score the machine, and name the stack it found: Secure Boot, MAC layer, firewall backend, sshd and update manager.
- Offer a fix for every finding whose right answer is the same on every machine, previewed as an exact command line and confirmed first — and refuse the ones that would leave nothing able to log in.
- Re-run one probe or all of them, and filter across every field.
- Print the whole posture as JSON for a script or a test.
- Follow the active Omarchy theme, and respect
NO_COLOR.
- No history. It reads the machine now; it does not keep yesterday's answer to compare against. There is no state of its own, by design.
- No CIS or STIG benchmark. The rules here are the handful whose right answer is the same on a laptop, a server and a container host. A compliance benchmark is a different tool with a different audience.
- No auditd rule editing, no SELinux policy authoring, no
semanage/setsebool, no AppArmor profile editing. - No per-user account audit.
sudo -n -lanswers for the user running the tool, which is the honest scope without reading the whole sudoers tree as root. - No file integrity check, no rootkit scan, no CVE matching against the installed packages — all of which need a database this tool does not ship and a network it does not open.
- Only
net.ipv4.ip_forwardis reported without a fix, because 1 is right on a router or a container host and wrong everywhere else, and this tool does not know which one you have.
tui-secure probes its backend once at startup and shows the version in the
header. A version nobody has tested is marked (untested) there rather than
hidden; one below the minimum is marked as such and the tool still runs.
| Binary | systemctl |
| Version read with | systemctl --version |
| Minimum | 245 |
| Tested | 255, 257, 259, 261 |
| Version-gated features | journal-grep (since 246) |
| Versions | What changes |
|---|---|
<246 |
journalctl --grep does not exist, so the denial and failed-login counts are read by filtering the journal here instead of in journalctl, which is slower on a large journal |
| Binary | sshd |
| Version read with | ssh -V |
| Minimum | 8.2 |
| Tested | 9.6, 9.9, 10.2, 10.5 |
| Version-gated features | sshd-test-config (since 8.2) |
| Versions | What changes |
|---|---|
<8.2 |
sshd -T predates several of the keywords this probe reads, so the settings come from parsing sshd_config and its drop-ins instead, which does not resolve Match blocks |
| Binary | ufw |
| Version read with | ufw --version |
| Minimum | 0.36 |
| Tested | 0.36.2 |
| Versions | What changes |
|---|---|
<0.36 |
ufw status verbose prints no default policy line, so the incoming and outgoing policies are reported as unknown and only the active state is judged |
| Binary | firewall-cmd |
| Version read with | firewall-cmd --version |
| Minimum | 0.9 |
| Tested | 2.3.2, 2.4.4 |
| Binary | sbctl |
| Version read with | sbctl version |
| Minimum | — |
| Tested | none yet |
| Versions | What changes |
|---|---|
>=0.1 |
sbctl is optional: without it the Secure Boot probe reports what bootctl status knows and says nothing about enrolled keys or signed files |
The tested versions are generated from compat/results.jsonl, which the tool's
own smoke test appends to when it runs against a real machine in
tui-lab.
/etc/tui-secure/config.toml, then ~/.config/tui-secure/config.toml (the
user file overrides the machine-wide one), then TUI_SECURE_* in the
environment. Flags override everything. See
examples/config.toml.
# Privilege escalation prefix; "" runs the command directly.
sudo = "sudo -n"
# Path to an Omarchy-style colors.toml; empty follows the active theme.
theme = ""The default palette is Tokyo Night. On Omarchy, the tool reads the active
desktop theme from ~/.config/omarchy/current/theme/colors.toml and follows it.
TUI_THEME or --theme override; NO_COLOR drops color and keeps layout. The
rules live in tui-kit.
The UI never builds a bootctl or ufw command line. It talks to
internal/posture.Backend, which returns a machine-neutral report:
Report{Distro, Kernel, Stack, Probes, Score, Worst}
Probe{ID, Title, Status, Summary, Reason, Findings, Evidence, Raw, Fix, Actions}
Evidence{Command, Line} Fix{Hint, Tool, Command}
internal/host is the only package that starts a process. It drives every
program through a kit runner, and
check-exec.sh
fails the build if any other package imports os/exec.
The fixes are runner.Command values produced by the backend. The UI
shows them and, on confirmation, hands the same ones back to the
kit runner,
which resolves the binary and the privilege prefix. That is the whole trust
boundary, and it is why the preview is guaranteed to match what executes.
make check # gofmt, go vet, the exec boundary and the tests: what CI runs
make test
make build
make demo
make screenshots # re-render the frames above from --demoThe parser tests run against captured output: half of it from a real Fedora 42
machine, half written to match what the tools print on a distribution that
machine is not — an Ubuntu with ufw and AppArmor, an Arch with checkupdates,
an sshd that still takes passwords. See internal/host/testdata.
Dependencies are deliberately small: Bubble Tea, Bubbles and tui-kit, which carries the palette, the widgets, the config loader and the command runner shared by the whole family.
- Enabling ufw on a machine you are connected to over the network will end the session if ufw has no rule for ssh. The confirm dialog says so before you agree.
- The
/etc/sysctl.d/90-tui-secure.confdrop-in is this tool's own file, and each line in it was agreed to once. Setting a second key keeps the first; removing a line and runningsudo sysctl --systemundoes it. /etc/ssh/sshd_config.d/50-tui-secure.confis this tool's own file too, and it is regenerated in full on every change: a keyword agreed to earlier survives, a keyword this tool does not own is not carried forward. It is checked withsshd -t -fbefore it is installed, so a file the server would refuse never reaches/etc. Deleting a line and runningsudo systemctl reload sshdundoes it. sshd takes the first value it is given for a keyword, so when a drop-in sorting earlier already sets one, the dialog names that file instead of quietly doing nothing.- Stopping the unit behind a listening port is a decision about what the machine is for, not a hardening default: it is offered, never suggested, and never for the ssh server.
- The accounts probe reads
/etc/shadowwhen it can. It keeps the count and the account names, never the hashes: nothing from that file reaches the screen, the--checkoutput or a log. - The update probe runs the distribution's own update check, which contacts the
configured package repositories.
tui-secureitself opens no connection. tui-securere-reads a probe after every change, so what you see is what the system reports, not what the tool assumed.
Contributions arrive as pull requests, and the guide the whole family follows is CONTRIBUTING.md in tui-kit: it covers the branch, the checks a change has to pass and how the commits are written. A vulnerability goes to SECURITY.md instead, never into a public issue.






