This page summarizes which ANSSI rules are activated , according to the level and
category of a host. It is the reference to consult before hardening a machine.
Understand first
The concepts used here (levels, categories, labels, MAC, egress…) are
explained in Security
notions .
darkone . system . security = {
level = " intermediary " ; # minimal | intermediary | reinforced | high
category = " server " ; # base | client | server
Levels are cumulative : intermediary adds its rules to those of
minimal, and so on. The category adds the rules specific to a workstation
(client) or a server (server).
Profile Recommended category Why minimal, portablebaseUniversal base desktop, laptopclientScreen, USB, interactive session server, gateway, hcs, vmserverExposed services, network
Each column shows the rule’s state at that level (levels being
cumulative). Legend:
Icon Meaning ✅ Active at this level ⚠️ Partial (see note) ❌ Not yet active at this level
Code Rule Min Inter Rein High R3UEFI Secure Boot (if supported) ❌ ⚠️ ⚠️ ⚠️ R5Bootloader password ❌ ⚠️ ⚠️ ⚠️ C6LUKS2 disk encryption ❌ ⚠️ ⚠️ ⚠️ R7IOMMU enabled ❌ ❌ ✅ ✅ R29/boot restricted❌ ❌ ✅ ✅ R6cmdline and initramfs protection ❌ ❌ ❌ ✅
R3, R5, C6: warning, no boot reconfiguration
From intermediary onward, these rules emit a warning when the protection
is not detected, but do not reconfigure the boot. Secure Boot (lanzaboote, key
enrollment) and LUKS encryption are declared in the host disk configuration
(disko); the module only signals their absence. The future checkScript will
verify their actual state.
Code Rule Min Inter Reinforced Elevated R8Memory options on the command line ❌ ✅ ✅ ✅ R9Hardening sysctls ❌ ✅ ✅ ✅ R11Yama: restricted ptrace ❌ ✅ ✅ ✅ R10Module loading disabled ❌ ❌ ✅ ✅ R60linux_hardened kernel❌ ❌ ✅ ✅ C1linux-hardened patches ❌ ❌ ❌ ✅ C2LSM Lockdown ❌ ❌ ❌ ✅ R15–R27Compile-time hardening ❌ ❌ ❌ ✅
R9: user namespaces remain allowed
The legacy key kernel.unprivileged_userns_clone comes from a Debian patch and is absent from a mainline kernel: setting it causes systemd-sysctl to fail. The mainline equivalent user.max_user_namespaces = 0 breaks Flatpak, bubblewrap, and rootless Podman. It is therefore optional :
darkone.security.kernel-params.restrictUserns = true, to be reserved for servers without application sandboxing.
Code Rule Min Inter Reinforced Elevated R30Unused accounts disabled ✅ ✅ ✅ ✅ R31Strong passwords ✅ ✅ ✅ ✅ R68Password storage in yescrypt ✅ ✅ ✅ ✅ R32Idle session lockout ❌ ✅ ✅ ✅ R33Admin action audit trail ❌ ✅ ✅ ✅ R34Service accounts without shell ❌ ✅ ✅ ✅ R35Unique service accounts ❌ ✅ ✅ ✅ R67Secure remote PAM ❌ ⚠️ ⚠️ ⚠️ R69User databases over TLS ❌ ⚠️ ⚠️ ⚠️ R70System accounts ≠ directory ❌ ⚠️ ⚠️ ⚠️ C10Session limits (nproc, nofile) ❌ ✅ ✅ ✅
R69, R70: remote directory deferred
These rules only concern a remote directory (SSSD/nslcd), not used by DNF today; their TLS component is deferred (future checkScript).
R32: idle session logout
TMOUT=600 closes any console or SSH session that has been idle for ten minutes, on both bash and zsh — DNF makes zsh the default login shell, and the variable was previously only set for bash. A tmux or screen left in the foreground is terminated with the session.
The variable is set as readonly: it cannot be disabled in a session. A machine that keeps long-running terminals (compilation farms, agent tools) sets darkone.security.users.idleTimeout = 0, which removes the TTY delay without affecting GNOME lock.
R67: pam_faillock truly locks accounts
Three incorrect passwords lock the account for 15 minutes on login, su, sudo, and through the login stack, the GNOME lock screen. The stack is complete: preauth rejects, authfail counts, the account rule blocks an already locked account. The two auth rules bracket the pam_unix for each service ; their rank is calculated as it differs from one stack to another (11700 on su/sudo, 13100 on login).
sshd is intentionally excluded: DNF prohibits password authentication, so faillock would never count anything there. Its account rule could only reject a backup key session for a lock acquired at the console. Maintenance access therefore remains open under all circumstances, and nix (without password credentials) cannot be locked.
Keep a root session open during the initial deployment and know the recovery command: faillock --user <login> --reset. Settings: darkone.security.pam.faillock.deny and .unlockTime. root is never locked.
Code Rule Min Inter Reinforced Elevated R39Hardened sudo directives ❌ ✅ ✅ ✅ R40Non-root sudo targets ❌ ✅ ✅ ✅ R42Deny list prohibition ❌ ✅ ✅ ✅ R44sudoedit for editing❌ ✅ ✅ ✅ R38Dedicated sudo group ❌ ❌ ✅ ✅ R41NOEXEC override restrictions❌ ❌ ✅ ✅
R39: without noexec or requiretty
These two directives are incompatible with a managed deployment: colmena escalates via sudo -H -- without a PTY, and switch-to-configuration spawns child processes. R39 therefore relies on use_pty, log_input, and log_output, which provide the desired audit trail. Session recordings accumulate in /var/log/sudo-io, rotated weekly with 12 archives retained.
Code Rule Min Inter Reinforced Elevated R53Detection of unowned files ✅ ✅ ✅ ✅ R54Sticky bit on /tmp and /var/tmp ✅ ✅ ✅ ✅ R56Detection of setuid outside allowlist ✅ ✅ ✅ ✅ R14Filesystem sysctls ❌ ✅ ✅ ✅ R28Partitioning and mount options ❌ ✅ ✅ ✅ R50Sensitive file restrictions ❌ ✅ ✅ ✅ R52Socket and named pipe permissions ❌ ⚠️ ⚠️ ⚠️ R55Per-user temporary directories ❌ ⚠️ ⚠️ ⚠️ R36UMASK 0077❌ ❌ ✅ ✅ R57Minimal root setuid (capabilities) ❌ ❌ ✅ ✅
R14: binfmt_misc kept on emulating hosts
Disabling binfmt_misc breaks cross-compilation (aarch64 images for Raspberry Pi). The restriction is automatically lifted when boot.binfmt.emulatedSystems is not empty; the needs-binfmt label remains useful for other cases (Java, qemu-static).
R28: /tmp non-executable
/tmp is a tmpfs with nosuid,nodev,noexec, sized via boot.tmp.tmpfsSize (25% of RAM by default). R28 owns the mount : boot.tmp.useTmpfs is forced to false, otherwise the tmp.mount unit it generates — without noexec — would silently override /etc/fstab.
noexec forbids direct execution and dlopen. It therefore breaks any tool that executes what it just decompressed (pip, cargo, gcc) and any runtime that extracts its native libraries into /tmp (Bun, Electron, agent tools). Nix builds are covered, the daemon switching to /var/tmp, but an interactive tool may require export TMPDIR=$XDG_RUNTIME_DIR.
Two fallback options per host:
Option Effect darkone.security.filesystem.tmpNoexec = falsekeeps the tmpfs with nosuid,nodev, removes noexec darkone.security.filesystem.tmpfs = falseleaves /tmp on its original filesystem (persistent data, no RAM consumed)
R28, R52, R55: designated services and partitions
R28 also hardens /proc (hidepid=2) and /dev/shm. The proc group, which grants the right to see other users’ processes, is populated with wheel members; otherwise ps, htop, and the GNOME monitor would display nothing for administrators themselves. Mount options on /var/log, /srv, /opt, /home are added via darkone.security.filesystem.extraMountHardening (separate partitions required). R52 (directory mode 0750) and R55 (per-service private /tmp) apply to units listed in darkone.security.services.hardenedUnits — empty by default. See R63.
Code Rule Min Inter Rein High R62Obsolete services disabled ✅ ✅ ✅ ✅ R63Reduced service capabilities ❌ ⚠️ ⚠️ ⚠️ R37Mandatory access control (MAC) ❌ ❌ ✅ ✅ R45AppArmor profiles ❌ ❌ ✅ ✅ R64Restricted service privileges ❌ ❌ ✅ ✅ R65Service isolation (namespaces) ❌ ❌ ✅ ✅ R66Container hardening ❌ ❌ ❌ ✅
R63: opt-in sandboxing per service
Full systemd confinement (ProtectSystem=strict, system call filters,
MemoryDenyWriteExecute, UMask=0027…) applies to units declared in
darkone.security.services.hardenedUnits. Empty by default : blind
hardening would break services. The list acts as a floor , overridable per
unit, and needs-jit lifts MemoryDenyWriteExecute for JIT engines.
Code Rule Min Inter Reinforced High R80Listening ports monitoring ✅ ✅ ✅ ✅ C4Firewall default-deny ⚠️ ⚠️ ✅ ✅ R12IPv4 network sysctls ❌ ✅ ✅ ✅ R13Disabling IPv6 (on request) ❌ ⚠️ ⚠️ ⚠️ C5OpenSSH hardening ❌ ✅ ✅ ✅ C7NTS time synchronization ❌ ✅ ✅ ✅ C8Secure DNS resolver (DNSSEC, DoT) ❌ ✅ ✅ ✅ R79Hardened exposed services (server) ❌ ✅ ✅ ✅ R78Network service isolation (server) ❌ ❌ ✅ ✅
C4 partial at the first two levels
At the minimal and intermediary levels, C4 covers inbound
filtering (default-deny). Outbound (egress) filtering is added from
reinforced onwards.
C5: no tunnel, no agent, no ssh -J
TCP tunnels, agent forwarding and X11 are disabled. ProxyJump falls with them:
ssh -J opens a direct-tcpip channel, which is exactly what
AllowTcpForwarding refuses. Administrative access happens directly on the
tailnet , never through a jump.
On the tailnet, SSH follows the workstation
The tailnet access policy only opens port 22 to administration workstations
and to devices declared in adminDevices : an administrator account is not
enough. See Access policy .
R13: disabling IPv6 is an explicit request
An exclusion label only knows how to remove a rule. no-ipv6 therefore made
R13 active by default and cut IPv6 across the whole fleet. Disabling now goes
through darkone.security.kernel-params.disableIpv6 (false by
default); ANSSI prefers hardening IPv6 over removing it anyway.
R12: reverse-path filtering matched to the role
Strict mode (rp_filter = 1) drops any asymmetric reply. A tailscale
subnet router advertises its routes without SNAT
(--snat-subnet-routes=false): this is exactly that case. isGateway
or isExitNode hosts switch to loose mode (2), the others stay strict.
R79: every exposed service must have its filter
fail2ban runs on the systemd backend: a jail requires a filter.d
definition and a journalmatch, otherwise the daemon refuses to start —
and takes the sshd jail down with it. exposedServices is therefore checked
at evaluation against darkone.security.network.filters. Provided today:
sshd (shipped with fail2ban) and vaultwarden. Caddy, Matrix and Kanidm need
patterns written from real journal logs.
Code Rule Min Inter Renf High R71Persisting sealed logging ❌ ❌ ✅ ✅ R72Dedicated logs per service ❌ ❌ ✅ ✅ R73auditd with ANSSI rules ❌ ❌ ✅ ✅ C9Core dumps disabled ❌ ❌ ✅ ✅ R76Sealing and integrity verification ❌ ❌ ❌ ✅ R77Sealed base protection ❌ ❌ ❌ ✅
Code Rule Min Inter Hard High C11Legal access banners ✅ ✅ ✅ ✅ C12cron / at restricted✅ ✅ ✅ ✅ R61Regular updates ⚠️ ⚠️ ⚠️ ⚠️ R74Hardened local mail ❌ ✅ ✅ ✅ R75Mail alias (root → admin) ❌ ✅ ✅ ✅ R51Installation secret rotation ❌ ❌ ✅ ✅ C3USBGuard (workstation) ❌ ❌ ✅ ✅
Three levers allow fine-tuning a specific protection without lowering the global level.
Tags (excludes): disable a group of incompatible rules (e.g. needs-jit, needs-binfmt).
Exceptions (exceptions): bypass a single rule, with mandatory justification.
Activation options : enable a rule too destructive to be active by default (disableIpv6, restrictUserns).
The details of these levers are in Security
Concepts .
Module under stabilization
Raise the level gradually and test each host before production.