yuzu

system_hardening

What it doesExploit-mitigation and kernel-hardening posture (read-only)
Version1.0.0
KindCollector · read-only · gathered (crossplatform.system_hardening.posture)
PlatformsWindows ✅ · macOS ✅ · Linux ✅
Actionsposture (definition crossplatform.system_hardening.posture)
Securitysecurable Security · operation Read · risk Low · dispatch ReadOnly · approval gate None
Rolesexecute: endpoint-admin, endpoint-operator, security-admin · author: content-author

How it works

posture reads a fixed list of exploit-mitigation and kernel-hardening settings and writes one row per setting: posture|<os>|<key>|<raw>|<state>. It takes no parameters, so no request text reaches a path or a registry name, and it never starts a process. Linux reads 11 /proc/sys files, macOS reads 4 sysctl values with sysctlbyname, and Windows decodes two system-wide mitigation registry values and asks Windows about the agent’s own process. Each key reads as a value with a state, as absent (the OS definitively says the key is not there) or as unreadable (the read failed). A failed read is never reported as absent, and a not-found counts as absence only where the surface being read is itself confirmed present. The state describes the hardening, not the raw number: kernel.sysrq=0 is enabled, kern.coredump=1 is disabled.

flowchart LR
  OP["Operator or workflow"] --> SRV["Server, authz Security Read"]
  SRV -->|"gRPC over mTLS"| EX["system_hardening execute: posture"]
  EX --> WIN["Windows leg: RegQueryValueExW on MitigationOptions and MitigationAuditOptions under Session Manager kernel, GetProcessMitigationPolicy on the agent process"]
  EX --> MAC["macOS leg: sysctlbyname on 4 allowlisted keys"]
  EX --> LIN["Linux leg: reads of 11 allowlisted proc sys files, statfs procfs check before a not-found counts as absent"]
  WIN & MAC & LIN --> ROWS["posture rows with raw value and state, status OK, CONSTRAINED or PERMISSION_DENIED"]
  ROWS --> RS[("ResponseStore")] --> API["REST /api/responses"]

What is read and how absence is decided

Absence is not a failure: an absent row adds no reason token and does not lower the result status. A host that lacks an optional key (Linux without Yama, a default Windows install with no MitigationOptions) reports OK. Each unreadable row carries one reason token. The one token that belongs to no row is the Linux backstop proc_sys:not_visible: all eleven keys ENOENT on a confirmed procfs keeps every row absent but reports CONSTRAINED.

What you can decide with it

QuestionWhere to lookWhat a result means
Is memory-layout randomization and exploit hardening on?Linux kernel.randomize_va_space, kernel.kptr_restrict, fs.suid_dumpable; Windows mitigation.dep, mitigation.aslr_*, mitigation.cfgLinux and macOS states read directly (enabled, partial, disabled). On Windows, default or absent means no system-wide override is set and the OS defaults apply; it does not mean unprotected
Can local users inspect or attach to other processes?Linux kernel.yama.ptrace_scope, kernel.dmesg_restrict, kernel.kptr_restrict, kernel.unprivileged_bpf_disabledptrace_scope absent means the kernel does not expose the Yama setting (not built in or not active); the plugin cannot tell which
Are link and temp-directory tricks blocked?Linux fs.protected_*The four keys are independent; disabled on any one leaves that class open
Are core dumps and boot overrides locked down?macOS kern.coredump, kern.sugid_coredump, kern.bootargs; Linux fs.suid_dumpableA non-empty macOS kern.bootargs is unmodelled and the raw column carries the whole string
Can the keyboard trigger kernel functions (reboot, crash dump, process kill)?Linux kernel.sysrqenabled is 0 (all off), disabled is 1 (all on), partial is a bitmask of allowed functions
Is the agent process itself hardened?Windows self.*These describe the agent process only, not the host

Linux keys. The state follows the kernel’s documented value meanings.

KeyWhat it limitsenabledpartialdisabled
kernel.randomize_va_spacePredictable memory addresses for exploits (ASLR)2 full randomization10 off
kernel.kptr_restrictKernel addresses visible to local users2 hidden from all1 hidden from unprivileged users0 exposed
kernel.yama.ptrace_scopeOne process attaching to another’s memory1 to 3 (descendants only, admin only, or none)-0 any same-user process
kernel.dmesg_restrictReading the kernel log without privilege1-0
kernel.unprivileged_bpf_disabledUnprivileged eBPF programs1 or 2-0 allowed
kernel.sysrqKeyboard-triggered kernel functions0 all off2 to 511 a function bitmask1 all on
fs.protected_hardlinksHard-link abuse in world-writable sticky directories1-0
fs.protected_symlinksSymlink-following abuse in world-writable sticky directories1-0
fs.protected_fifosOpening other users’ FIFOs in sticky directories2 (also group-writable)1 (world-writable)0
fs.protected_regularOpening other users’ regular files in sticky directories2 (also group-writable)1 (world-writable)0
fs.suid_dumpableCore dumps of setuid programs leaking memory0 no dump2 root-readable only1 any dump

macOS keys. kern.securelevel: 2 is enabled, 1 is partial, -1 or 0 is disabled (the captured Mac reads 0, so its row is disabled). kern.coredump and kern.sugid_coredump: 0 is enabled (dumps off), 1 is disabled. kern.bootargs: empty is enabled (no boot-argument overrides), anything else is unmodelled.

Windows policies. One registry value decodes into one row per policy, because the first 8 bytes are a map of 16 two-bit nibbles in the kernel’s policy order: dep, sehop, force_relocate_images, heap_terminate, aslr_bottom_up, aslr_high_entropy, strict_handle_checks, win32k_system_call_disable, extension_point_disable, prohibit_dynamic_code, cfg, block_non_microsoft_binaries, font_disable, image_load_no_remote, image_load_no_low_label, image_load_prefer_system32. A nibble is a tri-state override: 0 is default (not set), 1 is on, 2 is off, and 3 to 15 are unmodelled. Only dep, sehop, aslr_bottom_up, aslr_high_entropy and cfg (and the value 1) are confirmed on hardware; the other positions are inferred by order. Each further 8 bytes of a longer value appear as an ext_q<n> row (default when zero, otherwise unmodelled). The self.* rows read the agent’s own mitigation flags: self.dep, self.aslr_bottom_up, self.aslr_force_relocate, self.aslr_high_entropy, self.cfg, self.cfg_export_suppression, self.cfg_strict_mode, each on or off, with the raw flags word in decimal.

What it cannot tell you. The plugin does not report Linux LSM state (SELinux, AppArmor), macOS SIP or Gatekeeper (see platform_security), the mitigations of any process other than the agent, or whether a Windows override is effective on a given binary. OK means no key failed to read; it is not a hardening score.

Diagrams

Linux read of one key

flowchart TD
  A["open the key file read-only and non-blocking"] --> B{"open result"}
  B -->|"opened"| C{"regular file?"}
  C -->|"no"| U1["unreadable, key:not_regular"]
  C -->|"yes"| V["read at most 256 bytes, trim, map to a state"]
  B -->|"ENOENT"| D{"nearest existing directory up to /proc/sys is procfs?"}
  D -->|"yes"| AB["absent, no token"]
  D -->|"no"| U2["unreadable, key:errno_19"]
  D -->|"statfs refused"| U3["unreadable, key:eacces"]
  B -->|"EACCES or EPERM"| U4["unreadable, key:eacces, status PERMISSION_DENIED"]
  B -->|"other errno"| U5["unreadable, key:errno_n"]
  AB --> Z["all 11 keys absent adds proc_sys:not_visible"]

This is the check that stops a hidden or replaced /proc/sys (a ProcSubset=pid mount, a tmpfs overmount, a container that does not expose it) from reading as “every mitigation absent”. The all-eleven-absent backstop covers the remaining case of a confirmed procfs that has none of the keys. A FIFO or device placed at a key path is handled without blocking.

Windows decode

flowchart TD
  K["open Session Manager kernel key"] -->|"not found"| KM["both registry rows unreadable, name:key_missing"]
  K -->|"opened"| Q["read MitigationOptions and MitigationAuditOptions"]
  Q -->|"value not found"| A["absent row, no token"]
  Q -->|"REG_BINARY or 8-byte REG_QWORD"| N["first QWORD into 16 nibbles, extra QWORDs as ext_q rows"]
  Q -->|"other type or bad size"| U["unreadable, name:type_n or a decoder token"]
  P["GetProcessMitigationPolicy for DEP, ASLR, CFG on the agent process"] -->|"success"| S["self.dep, self.aslr_*, self.cfg* rows"]
  P -->|"ERROR_NOT_SUPPORTED"| A2["absent row"]
  P -->|"ERROR_INVALID_PARAMETER"| U2["unreadable, name:win32_87"]

The registry half reports the system-wide override; the process half reports the agent’s own effective flags. On a default install the two registry values do not exist, so both rows read absent and the run is OK.

OS capability

ActionWindowsmacOSLinux
posture✅ supported · rung 1 · HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\kernel mitigation registry + GetProcessMitigationPolicy (agent process)✅ supported · rung 1 · allowlisted sysctlbyname reads (kern.securelevel/coredump/sugid_coredump/bootargs)✅ supported · rung 1 · allowlisted /proc/sys reads (open/read, errno-classified absent/unreadable; a missing key is absent only when statfs confirms its nearest existing directory is procfs)

Declared limits per leg (descriptor fallback text, verbatim):

Privileges and prerequisites

OSRuns asExtra grant neededMeasuredIf the read is refused
Windowsagent service account (LocalSystem today, #1442)None documented for this plugin (docs/agent-privilege-model.md)NT AUTHORITY\SYSTEM on a bare-metal Windows 11 Pro host (10.0.26200): probe 2026-09-21, sample captured 2026-09-24 (docs/samples/windows.txt); the mitigation probe summary is in the system_hardening_win.cpp bannerERROR_FILE_NOT_FOUND/ERROR_PATH_NOT_FOUND on a registry value reports that row absent with no token (the default state of a fresh install), as does ERROR_NOT_SUPPORTED from GetProcessMitigationPolicy for that self.* row; ERROR_INVALID_PARAMETER reports it unreadable with <name>:win32_87; ERROR_ACCESS_DENIED reports it unreadable with <name>:access_denied and the action reports PERMISSION_DENIED/PARTIAL; any other Win32 error reports unreadable with <name>:win32_<n>. A not-found on the Session Manager\kernel key itself reports both registry rows unreadable with <name>:key_missing and the action reports CONSTRAINED/PARTIAL: that key exists on every install, so its absence is never a clean answer
macOSagent daemon, root today (LaunchDaemon carries no UserName key, docs/agent-privilege-model.md TL;DR); this plugin’s reads need no elevation beyond that defaultNone; sysctlbyname reads of these keys are unprivileged2026-09-23, bare-metal Mac, unprivileged (euid 501): real capture (docs/samples/macos.txt). No root-privileged run has been captured, so the root identity above is the documented production default, not a measured oneEPERM reports the key unreadable with reason <key>:eacces and the action reports PERMISSION_DENIED/PARTIAL; an unknown oid (ENOENT) reports it absent with no token and leaves the result unchanged
Linuxagent daemon, dedicated unprivileged account (the agent never runs as root on Linux)None; the allowlisted /proc/sys files are world-readable on current kernels. On older kernels the four fs.protected_* files were root-only (mode 0600), so a non-root agent there reads them unreadable (<key>:eacces) and the action reports PERMISSION_DENIED; that is from kernel history and was not measured here2026-09-23, aarch64 Debian 13 container, root (euid 0): a real live capture of the built .so (docs/samples/linux.txt). Further hand runs during development (a tmpfs over /proc/sys and a subset=pid procfs each gave eleven errno_19 rows and CONSTRAINED/PARTIAL; a tmpfs over /proc/sys/kernel made only the six kernel.* keys unreadable; all ten keys present on kernel 7.0 read world-readable, mode 0644, as a non-root user, uid 65534, in a container) are not captured in a committed file; the surface check itself is covered by a fake-statfs unit test (tests/unit/test_system_hardening_parsers.cpp)EACCES/EPERM reports the key unreadable with reason <key>:eacces and the action reports PERMISSION_DENIED/PARTIAL; a leaf that is not a regular file reports unreadable with <key>:not_regular; any other errno reports unreadable with <key>:errno_<n>; a missing file (ENOENT, for example kernel.yama.ptrace_scope on a kernel that does not expose Yama) reports it absent with no token, but only once statfs confirms the nearest existing directory above it is a mounted procfs, otherwise it is remapped to ENODEV and reads unreadable with <key>:errno_19 (a refused confirmation statfs is never downgraded to ENODEV; it reads unreadable with <key>:eacces). If ALL ELEVEN keys read ENOENT on a confirmed procfs, one additional proc_sys:not_visible token downgrades the result to CONSTRAINED (each row still reads absent)

Binaries and subprocesses: none. Every leg is an in-process read (/proc/sys, sysctlbyname, the Windows registry API and GetProcessMitigationPolicy); no sysctl, reg or PowerShell binary is run. Network: none.

Data contract

Inputs

The action takes no parameters.

Outputs

Pipe-delimited rows, one per allowlisted key, in allowlist order, written via write_output(). The first field is the fixed literal posture, then <os>, <key>, <raw> and <state>. <raw> is - when nothing was read (absent or unreadable); a raw value that was read passes through the shared untrusted-output escaper, with newlines and NULs folded to spaces so no value can split or cut a row. A Linux value is judged whole (whitespace trimmed from both ends), never on its first line, and emptiness on macOS is judged on the whole value, so "\n-v" is unmodelled, not enabled.

crossplatform.system_hardening.posture — row_kind|os|key|raw|state

FieldTypeValuesAvailableExampleDescription
row_kindstringposture constrainedWindows, Linux, macOSpostureRow family: posture (one per allowlisted key) or constrained (an internal-error row: os is the host leg, key is internal_error, state is unreadable).
osstringlinux macos windowsWindows, Linux, macOSlinuxThe leg that produced the row.
keystring-Windows, Linux, macOSkernel.randomize_va_spaceAllowlisted key: a sysctl name (Linux, macOS) or a policy name (Windows: mitigation. and mitigation_audit. from the registry, self. from the agent process; a registry value that was not decoded (absent or unreadable) reports under mitigation_options or mitigation_audit_options, and a failed self policy under self.dep, self.aslr or self.cfg; the trailing QWORDs of a longer registry blob appear as ext_q rows).
rawstring-Windows, Linux, macOS2The raw value read (integer, string, or 0x-prefixed hex on Windows); ”-” when nothing was read (absent or unreadable). An empty macOS kern.bootargs is a successful read and an empty column.
statestringenabled disabled partial on off default unmodelled absent unreadableWindows, Linux, macOSenabledLinux and macOS: enabled/disabled/partial describe the hardening (enabled = protection on, partial = on at a weaker level), not the sysctl value. Windows: on/off/default, because a system-wide mitigation nibble is a tri-state override (not set / always on / always off), not a hardening level. unmodelled = a value was read that this table has no interpretation for. absent = the key does not exist here (only where the surface being read is confirmed present). unreadable = the read failed, or the surface itself (a hidden /proc/sys, a missing Session Manager\kernel key) was not there.

Result status

StatusCompletenessProvenanceWhen
OKFULL—No key was unreadable: every key was either read or absent, except that all eleven Linux keys absent on a confirmed procfs is the proc_sys:not_visible backstop (CONSTRAINED, next row). An absent key adds no reason token, so a host that lacks an optional key (Yama not exposed by the Linux kernel, MitigationOptions never written on a default Windows install) still reports OK.
CONSTRAINEDPARTIAL<key>:errno_<n>, <key>:not_regular, <name>:win32_<n>, <name>:key_missing, <name>:type_<n>, <name>:oversized, proc_sys:not_visibleAt least one key was unreadable, or the Linux backstop fired, and none was refused. The reason lists one <key>:<cause> token per unreadable key, whose row reads unreadable; proc_sys:not_visible alone means all eleven Linux keys read absent on a confirmed procfs, with no unreadable row. Windows decoder failures (empty_blob, odd_length, not_qword_aligned, oversized) appear as <name>:<token>.
CONSTRAINEDPARTIALinternal_errorAny leg: an unexpected exception escaped a leg and was contained in execute(); the action returns 1 with the row constrained|<os>|internal_error|-|unreadable.
PERMISSION_DENIEDPARTIAL<key>:eacces, <name>:access_deniedAny leg: a read was refused — EACCES/EPERM on Linux and macOS (token <key>:eacces) or ERROR_ACCESS_DENIED on Windows (token <name>:access_denied). A refusal outranks every other unreadable cause in the same run; the reason still lists every unreadable key’s token.

Where the data goes

Sample output

Windows — captured: windows Windows 10.0.26200 x86_64 · bare-metal · 2026-09-24 · LocalSystem (elevated) · leg-hash e678758a486f

== action=posture
posture|windows|mitigation_options|-|absent
posture|windows|mitigation_audit_options|-|absent
posture|windows|self.dep|3|on
posture|windows|self.aslr_bottom_up|5|on
posture|windows|self.aslr_force_relocate|5|off
posture|windows|self.aslr_high_entropy|5|on
posture|windows|self.cfg|0|off
posture|windows|self.cfg_export_suppression|0|off
posture|windows|self.cfg_strict_mode|0|off
[result_status] OK / FULL

macOS — captured: macos macOS 26.6.2 arm64 · bare-metal · 2026-09-23 · euid 501 · leg-hash e678758a486f

== action=posture
posture|macos|kern.securelevel|0|disabled
posture|macos|kern.coredump|1|disabled
posture|macos|kern.sugid_coredump|0|enabled
posture|macos|kern.bootargs||enabled
[result_status] OK / FULL

Linux — captured: linux Debian GNU/Linux 13 (trixie) aarch64 · container · 2026-09-23 · euid 0 · leg-hash e678758a486f

== action=posture
posture|linux|kernel.randomize_va_space|2|enabled
posture|linux|kernel.kptr_restrict|0|disabled
posture|linux|kernel.yama.ptrace_scope|-|absent
posture|linux|kernel.dmesg_restrict|1|enabled
posture|linux|kernel.unprivileged_bpf_disabled|0|disabled
posture|linux|kernel.sysrq|1|disabled
posture|linux|fs.protected_hardlinks|1|enabled
posture|linux|fs.protected_symlinks|1|enabled
posture|linux|fs.protected_fifos|0|disabled
posture|linux|fs.protected_regular|0|disabled
posture|linux|fs.suid_dumpable|0|enabled
[result_status] OK / FULL

Caveats and known gaps

  1. Two state vocabularies. Linux and macOS report enabled, disabled or partial, while Windows reports on, off or default because a system-wide mitigation nibble is a tri-state override, not a hardening level, so consumers key on the <os> column. The three non-value states (unmodelled, absent, unreadable) are the same on every OS.
  2. absent is reported only where the surface is confirmed present, and it does not downgrade the run. A missing optional key (no Yama, no MitigationOptions on a default Windows install) adds no token and the run stays OK, while a hidden Linux surface or a missing Windows Session Manager\kernel key reads unreadable. The one exception is the Linux backstop: all eleven keys absent on a confirmed procfs adds proc_sys:not_visible and reports CONSTRAINED.
  3. Windows: the self rows are the agent process only, and only part of the mitigation layout is hardware-confirmed. The self.* rows describe the agent’s own process, while the system-wide policy is the mitigation.* registry rows. Five of the sixteen nibble positions and the nibble value 1 were confirmed on hardware; the other positions and values, trailing ext_q<n> QWORDs, every MitigationAuditOptions row, a REG_QWORD value and Windows on ARM64 (x64 emulation) are unmeasured.
  4. Fixed allowlists, no discovery; a container reads the kernel the agent runs on. The plugin reads only the keys listed above. The Linux keys are kernel-global, so a containerised agent (including the shipped deploy/docker/Dockerfile.agent image, where the Linux sample was captured) reports the kernel it runs on: the host’s under a shared-kernel runtime such as runc, the guest kernel’s under Kata or a microVM, and under gVisor a synthetic procfs that may implement only some of the keys (unmeasured here; the rest would read absent).
  5. A privileged local actor can still mislead the Linux read. The surface check detects a hidden or replaced /proc/sys, but root or CAP_SYS_ADMIN in the agent’s mount namespace can bind a real procfs directory or a forged regular file over a key, so the value reads as that file says. A filesystem that never answers, such as an unresponsive FUSE daemon, can make the read block, and no in-plugin bound can cancel a hung system call.

Source and tests