<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mattia Bandini</title>
    <description>The latest articles on Forem by Mattia Bandini (@mattiabandini1).</description>
    <link>https://forem.com/mattiabandini1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3858371%2F1dbf11b4-2614-4385-a2d7-cc54cbf616e0.jpeg</url>
      <title>Forem: Mattia Bandini</title>
      <link>https://forem.com/mattiabandini1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mattiabandini1"/>
    <language>en</language>
    <item>
      <title>SELinux AVC denied: stop guessing, start fixing</title>
      <dc:creator>Mattia Bandini</dc:creator>
      <pubDate>Fri, 17 Apr 2026 15:17:49 +0000</pubDate>
      <link>https://forem.com/mattiabandini1/selinux-avc-denied-stop-guessing-start-fixing-2oe9</link>
      <guid>https://forem.com/mattiabandini1/selinux-avc-denied-stop-guessing-start-fixing-2oe9</guid>
      <description>&lt;p&gt;It happens more often than not. A web server gets installed, maybe a database, or perhaps a fresh Podman container. Settings are adjusted carefully, one by one. For testing, permissions shift to &lt;code&gt;777&lt;/code&gt; — though never on live systems — yet somehow the error persists: &lt;strong&gt;Permission denied&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There it is — buried in the logs. That overwhelming chunk of text from SELinux stares back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type=AVC msg=audit(1612345678.123:456): avc:  denied  { read } for  pid=1234 comm="nginx" name="index.html" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now, many people online suggest simply typing &lt;code&gt;setenforce 0&lt;/code&gt; or turning off SELinux through &lt;code&gt;/etc/selinux/config&lt;/code&gt; without thinking twice. While that might stop the error fast, it weakens system security just to fix a configuration issue. Instead of removing protection entirely, learning what triggered the denial helps find safer fixes.&lt;/p&gt;

&lt;p&gt;Some opt for quick workarounds when logs show repeated denials. Yet skipping analysis often leads to broader risks later on. Adjusting policies carefully beats disabling them outright. A temporary bypass may seem helpful — until something else breaks silently.&lt;/p&gt;

&lt;p&gt;Here’s something worth considering: turning off SELinux strips away critical protection. When it comes to system safety, removing safeguards rarely helps. Rather than silencing warnings, understanding them makes more sense. Think about it: security tools are there for a reason. What if we focused on interpreting alerts instead? That shift could make all the difference.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Anatomy of an AVC Denial
&lt;/h3&gt;

&lt;p&gt;Imagine a doorkeeper who ignores names, focusing solely on wristbands. That is SELinux — it disregards regular user access levels entirely. Instead, what matters are assigned tags known as Contexts. Entry depends not on identity but on these specific markers. Rules follow the label, never the person behind it.&lt;/p&gt;

&lt;p&gt;Here is what the confusing log actually means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;comm="nginx": A single process moves forward, attempting an action under the name nginx. Like a guest approaching a restricted entrance, it seeks access where entry is controlled.&lt;/li&gt;
&lt;li&gt;{ read }: This refers to the operation it aimed to carry out.&lt;/li&gt;
&lt;li&gt;name="index.html": This is the target file or object.&lt;/li&gt;
&lt;li&gt;tclass=file: Indicates what kind of target is being referenced — could be a regular file, a folder, or even a communication endpoint on a network. Each type changes how operations are applied during processing.&lt;/li&gt;
&lt;li&gt;scontext=...:httpd_t:... (Source Context): A process running nginx carries a specific identifier. Labelled as httpd_t, it functions much like an access pass for a visitor. The scontext value reflects what the system sees when checking permissions. Considered part of SELinux enforcement, this tag controls how the process interacts with resources. Assigned automatically, the label determines allowed actions within security policies.&lt;/li&gt;
&lt;li&gt;tcontext=...:user_home_t:... (Target Context): A person enters a space only if they meet certain conditions — this file needs approval just like that guest. Access depends on meeting specific criteria, much like an exclusive area allowing entry under set terms. The tag given to index.html acts as such a gatekeeper, determining who fits inside.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Translation: Not allowed. A web server process attempted to open index.html marked as personal user data. Security policy blocked it immediately. Home areas stay off-limits to public-facing services by design. Reading such files breaks containment rules. The system enforces strict boundaries between service roles and private spaces. Permission revoked based on labeling mismatch alone.&lt;/p&gt;




&lt;h3&gt;
  
  
  Slow Fixes Take Time
&lt;/h3&gt;

&lt;p&gt;Most of the time, system administrators handle this issue by searching &lt;code&gt;semanage fcontext&lt;/code&gt; documentation page by page. Another path is feeding the log data into &lt;code&gt;audit2allow&lt;/code&gt; for interpretation.&lt;/p&gt;

&lt;p&gt;Most people misunderstand what &lt;code&gt;audit2allow&lt;/code&gt; actually does: it won’t repair incorrect file labels. Instead, it creates a tailored policy enabling the web server access to home folders. That approach weakens SELinux instead of strengthening it. Usually, the right move involves adjusting the file context through &lt;code&gt;restorecon&lt;/code&gt; or &lt;code&gt;chcon&lt;/code&gt;. Altering system-wide policies rarely makes sense.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Fast Way: selinux-explain
&lt;/h3&gt;

&lt;p&gt;One day, it just became too much — reading through audit.log by hand, hunting down the right &lt;code&gt;restorecon&lt;/code&gt; or boolean tweaks for messed-up labels. Tools such as &lt;code&gt;setroubleshoot&lt;/code&gt; help, true, yet they bring along Python processes and D-Bus connections. My stripped-down, isolated systems do not run those.&lt;/p&gt;

&lt;p&gt;A small program took shape: &lt;code&gt;selinux-explain&lt;/code&gt; — crafted fully in Rust, running without internet. It works through your terminal, staying fast by design. Built to explain SELinux rules locally, it avoids external dependencies. No cloud needed, just direct responses from your machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flszf6ypoevbp5fxx8vlh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flszf6ypoevbp5fxx8vlh.gif" alt=" " width="1200" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most times, people guess what went wrong — here, denied logs go straight in. Once inside, each refusal gets broken down, clear as speech anyone understands. A reason appears without jargon, showing exactly why access failed. From there, a precise instruction forms, tailored to resolve the issue safely.&lt;/p&gt;




&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Single static binary: No external libraries required during operation. Runs independently of system-installed components. Entire program contained within a single output. Does not rely on additional software at execution time.&lt;/li&gt;
&lt;li&gt;Offline by default: Works without internet access — ideal for protected networks where external connections are restricted.&lt;/li&gt;
&lt;li&gt;Context-aware: Because it recognizes patterns, containers get the :Z flag automatically. Web services often require specific permissions — this adjusts them without extra steps. For instance, when a server needs network access, the system enables it through policy changes. Knowing typical setups helps avoid common roadblocks. Configuration follows real-world usage rather than rigid rules. Each decision builds on what the environment actually does.&lt;/li&gt;
&lt;li&gt;Extensible: Powered by a community-driven rules.toml file.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Try it out
&lt;/h3&gt;

&lt;p&gt;You can grab the pre-compiled binaries for your architecture on the &lt;a href="https://github.com/mattiabandini1/selinux-explain/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;When SELinux stops your app, skip typing &lt;code&gt;setenforce 0&lt;/code&gt;. Instead, send the log through selinux-explain — watch how it decodes the message silently. Each error finds clarity without disabling security layers first.&lt;/p&gt;

&lt;p&gt;Should you come across a rare scenario the tool fails to identify, consider submitting a pull request. Alternatively, apply the &lt;code&gt;--report&lt;/code&gt; option to contribute toward growing its collection of rules.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cli</category>
      <category>selinux</category>
      <category>redhat</category>
    </item>
    <item>
      <title>I got tired of setenforce 0. So I built a tool in Rust to actually understand SELinux denials.</title>
      <dc:creator>Mattia Bandini</dc:creator>
      <pubDate>Thu, 02 Apr 2026 21:46:05 +0000</pubDate>
      <link>https://forem.com/mattiabandini1/i-got-tired-of-setenforce-0-so-i-built-a-tool-in-rust-to-actually-understand-selinux-denials-24ie</link>
      <guid>https://forem.com/mattiabandini1/i-got-tired-of-setenforce-0-so-i-built-a-tool-in-rust-to-actually-understand-selinux-denials-24ie</guid>
      <description>&lt;p&gt;Every Fedora user has been there.&lt;/p&gt;

&lt;p&gt;You're setting up nginx, or configuring a custom app, or mounting a Docker volume — and suddenly everything stops working. You check the logs and you find something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type=AVC msg=audit(1612345678.123:456): avc: denied { read } for pid=1234 
comm="nginx" name="index.html" dev="sda1" ino=12345 
scontext=system_u:system_r:httpd_t:s0 
tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And your brain just... stops.&lt;/p&gt;

&lt;p&gt;Most people at this point do one of two things: paste the log on StackOverflow and wait, or run &lt;code&gt;sudo setenforce 0&lt;/code&gt; and forget about it. I've done both. The second option is particularly dangerous because you're disabling the entire SELinux enforcement on your system just because you couldn't read a log line.&lt;br&gt;
There are existing tools — &lt;code&gt;sealert&lt;/code&gt;, &lt;code&gt;audit2why&lt;/code&gt;, the SELinux plugin for Cockpit. They're useful if you know what you're doing. But they all have friction: &lt;code&gt;sealert&lt;/code&gt; requires the &lt;code&gt;setroubleshootd&lt;/code&gt; daemon running in the background with D-Bus, &lt;code&gt;audit2why&lt;/code&gt; is part of a Python package that isn't always available on minimal or headless servers, and Cockpit requires a full Cockpit setup.&lt;br&gt;
I wanted something simpler. A single binary I could drop on any machine — a fresh VPS, an air-gapped server, a minimal Fedora install — and just pipe a log line into it.&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;selinux-explain&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;selinux-explain takes a raw SELinux AVC denial and turns it into something a human can actually act on.&lt;br&gt;
Instead of the wall of text above, you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚨 SELinux Denial Detected!
=============================

Who:  nginx
What: Tried to 'read' the target 'index.html'
Why:  A process with label 'httpd_t' is not allowed to access 
      objects with label 'user_home_t'

💡 Suggestion:
The web server is trying to read a file or directory labeled 'user_home_t'.
Fix its context with: `sudo restorecon -Rv &amp;lt;/path/to/index.html&amp;gt;`
Or set it manually: `sudo chcon -t httpd_sys_content_t &amp;lt;/path/to/index.html&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things: what happened, why SELinux blocked it, and what to actually do about it. No daemon, no internet, no Python.&lt;/p&gt;

&lt;p&gt;You can use it three ways:&lt;/p&gt;

&lt;h3&gt;
  
  
  Analyze the latest denial in your system log
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;selinux-explain &lt;span class="nt"&gt;--last&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Analyze a specific log line
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;selinux-explain &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"type=AVC msg=audit..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pipe directly from the audit log
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;nginx /var/log/audit/audit.log | selinux-explain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Rust
&lt;/h2&gt;

&lt;p&gt;I'm a second-year Computer Engineering student at the University of Bologna and Rust is the language I've been learning independently over the past year. This project felt like the right fit for a few reasons.&lt;br&gt;
The main one is the deployment model. A Rust binary compiles to a single static executable with no runtime dependencies. You &lt;code&gt;cargo build --release&lt;/code&gt;, copy the binary to &lt;code&gt;/usr/local/bin/&lt;/code&gt;, and you're done. No Python interpreter, no shared libraries to worry about, no &lt;code&gt;pip install&lt;/code&gt; in a system that might not even have pip. For a tool that targets sysadmins on production servers, this matters.&lt;br&gt;
The second reason is performance. Parsing log files with regex is fast in any language, but with Rust you also get predictable memory usage and no garbage collector pauses. For a CLI tool that might be piped across thousands of log lines, that's not irrelevant.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;p&gt;The architecture is straightforward — four modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parser.rs is the core. It takes a raw log line and extracts the relevant fields using a regex:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;rustlet&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;r#"denied\s*\{\s*(.*?)\s*\}.*?comm="(.*?)".*?name="(.*?)"
    .*?scontext=(\S+).*?tcontext=(\S+).*?tclass=(\S+)"#&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The result is an AvcData struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;rustpub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;AvcData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;scontext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;tcontext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;tclass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;explainer.rs takes an AvcData and produces a human-readable explanation. The interesting part is &lt;code&gt;get_specific_advice()&lt;/code&gt;, which matches on a tuple of (source_type, action, tclass) to give contextual suggestions:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;rustmatch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tclass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"httpd_t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"read"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"open"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"getattr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"dir"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// web server trying to read a file with wrong label&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"httpd_t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name_connect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tcp_socket"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// web server trying to make outbound network connection&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"container_t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// container trying to access host resource&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// generic fallback&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the "80/20" approach: cover the most common denial types first (httpd_t, container_t) and fall back gracefully for everything else. The plan is to eventually move these rules into an external &lt;code&gt;rules.toml&lt;/code&gt; file so the community can contribute new cases without touching Rust code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reader.rs handles reading from &lt;code&gt;/var/log/audit/audit.log&lt;/code&gt; for the &lt;code&gt;--last flag&lt;/code&gt; — it scans the file line by line and returns the most recent AVC denial.&lt;/li&gt;
&lt;li&gt;main.rs ties everything together with clap for argument parsing, and handles the three input modes: &lt;code&gt;--last&lt;/code&gt;, &lt;code&gt;--text&lt;/code&gt;, and stdin pipe detection via &lt;code&gt;io::stdin().is_terminal()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The reception
&lt;/h2&gt;

&lt;p&gt;I posted about this on r/redhat before even having a working version — just a day-0 post asking whether people would find it useful. The response surprised me: over 5K views and 14 comments in the first 24 hours, including a Red Hat employee who commented and pointed out a classic Unix anti-pattern in my example (I was using &lt;code&gt;cat file | grep&lt;/code&gt; instead of just &lt;code&gt;grep file&lt;/code&gt; — the infamous "useless use of cat"). That kind of immediate feedback from people who actually work with these systems every day is exactly what you want when building a tool like this.&lt;br&gt;
Several people pointed out &lt;code&gt;sealert&lt;/code&gt; and &lt;code&gt;audit2why&lt;/code&gt; as existing alternatives. Fair points — and I addressed them directly in the thread. The positioning isn't "this replaces everything" but "this works everywhere, with zero setup, offline."&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The immediate roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rules.toml&lt;/code&gt; support — an external file that maps &lt;code&gt;(source_type, action, tclass)&lt;/code&gt; tuples to suggestions, so the community can contribute new cases without needing to know Rust&lt;/li&gt;
&lt;li&gt;RPM package and COPR repository — once the tool is stable enough, proper distro packaging for Fedora and RHEL&lt;/li&gt;
&lt;li&gt;More covered types — &lt;code&gt;mysqld_t&lt;/code&gt;, &lt;code&gt;sshd_t&lt;/code&gt;, &lt;code&gt;smbd_t&lt;/code&gt; are next on the list&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The repo is at &lt;a href="//github.com/mattiabandini1/selinux-explain"&gt;selinux-explain&lt;/a&gt;. There's a pre-compiled binary on the Releases page if you don't want to build from source.&lt;br&gt;
If you run into a log line that doesn't parse correctly, open an issue with the raw log string. Every real-world case helps make the parser more robust — and if you have a fix that worked for you for a type that isn't covered yet, I'd love to hear it.&lt;/p&gt;

&lt;p&gt;The goal is simple: nobody should have to run setenforce 0 because they couldn't read a log file.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>rust</category>
      <category>security</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
