<?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>DEV Community: Nick Ciolpan</title>
    <description>The latest articles on DEV Community by Nick Ciolpan (@nickciolpan).</description>
    <link>https://dev.to/nickciolpan</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%2F53943%2F1801f3f7-cd14-48e7-8eb5-765a4a2942a7.png</url>
      <title>DEV Community: Nick Ciolpan</title>
      <link>https://dev.to/nickciolpan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nickciolpan"/>
    <language>en</language>
    <item>
      <title>Building a Docker debug TUI in Rust with output-aware follow-ups</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Sat, 09 May 2026 15:24:41 +0000</pubDate>
      <link>https://dev.to/nickciolpan/building-a-docker-debug-tui-in-rust-with-output-aware-follow-ups-3oi8</link>
      <guid>https://dev.to/nickciolpan/building-a-docker-debug-tui-in-rust-with-output-aware-follow-ups-3oi8</guid>
      <description>&lt;p&gt;Every time I debug a container I run the same loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;                       &lt;span class="c"&gt;# find the thing&lt;/span&gt;
&lt;span class="c"&gt;# squint, copy a name with the mouse&lt;/span&gt;
docker logs casely-postgres-1
docker inspect casely-postgres-1
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; casely-postgres-1 sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The container name doesn't change between those commands. The shell doesn't help me; I copy and paste — sometimes typoing a hash prefix.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;dux&lt;/strong&gt; — a single Rust binary with a TUI (ratatui) and a browser UI (axum), 105 curated &lt;code&gt;docker&lt;/code&gt; commands, and after every run it &lt;strong&gt;parses the output and prefills the next command's args&lt;/strong&gt;.&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%2Ft8eeq4tp3wgt2bvfeusm.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%2Ft8eeq4tp3wgt2bvfeusm.gif" alt="dux follow-up modal: after running docker ps -a, the logs follow-up auto-fills with the parsed container names; ↑/↓ cycles" width="800" height="492"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;nickciolpan/tap/dux
dux           &lt;span class="c"&gt;# terminal UI&lt;/span&gt;
dux serve     &lt;span class="c"&gt;# http://127.0.0.1:7878/dux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source: &lt;a href="https://github.com/nickciolpan/dux" rel="noopener noreferrer"&gt;github.com/nickciolpan/dux&lt;/a&gt;. MIT.&lt;/p&gt;

&lt;p&gt;The rest of this post walks through the two ideas that make it work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idea 1 — typed placeholders
&lt;/h2&gt;

&lt;p&gt;Each command in the catalog is a &lt;code&gt;const&lt;/code&gt; 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="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Cmd&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&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;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&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;long_desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// "docker logs {container}"&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&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;produces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;// what the stdout lists&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;follow_ups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// ids&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;template&lt;/code&gt; uses &lt;code&gt;{name}&lt;/code&gt; placeholders. Each placeholder maps to an &lt;code&gt;ArgKind&lt;/code&gt;:&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="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ArgKind&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Free&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Network&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Volume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;arg_kind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ArgKind&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"container"&lt;/span&gt;        &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ArgKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"source"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ArgKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"network"&lt;/span&gt;          &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ArgKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Network&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"volume"&lt;/span&gt;           &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ArgKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Volume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"service"&lt;/span&gt;          &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ArgKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Service&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="nn"&gt;ArgKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Free&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire kind system. Five real kinds plus &lt;code&gt;Free&lt;/code&gt; for raw text (file paths, env values, port numbers, etc.).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;produces&lt;/code&gt; says what the command's stdout contains:&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="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Produces&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Containers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Networks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Volumes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Services&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;docker ps&lt;/code&gt; is &lt;code&gt;Produces::Containers&lt;/code&gt;, &lt;code&gt;docker images&lt;/code&gt; is &lt;code&gt;Produces::Images&lt;/code&gt;. Most commands are &lt;code&gt;Produces::None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two annotations. Everything else falls out.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idea 2 — parse stdout into typed candidates
&lt;/h2&gt;

&lt;p&gt;After every run, &lt;code&gt;extract::extract(produces, stdout)&lt;/code&gt; returns a typed bag:&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="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Candidates&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;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;For &lt;code&gt;docker ps&lt;/code&gt;, the parser is small and fault-tolerant — skip the header row, then for each line take the first whitespace token (the ID) and the last (the NAME):&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="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;parse_containers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;data_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\t'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ps with --format 'table {{.ID}}\t{{.Names}}\t...'&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\t'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;str&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="nf"&gt;.first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;dedup_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.containers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;dedup_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.containers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;toks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="nf"&gt;.split_whitespace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;toks&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;dedup_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.containers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;toks&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;dedup_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="py"&gt;.containers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker images&lt;/code&gt; parses &lt;code&gt;REPOSITORY&lt;/code&gt;, &lt;code&gt;TAG&lt;/code&gt;, and &lt;code&gt;IMAGE ID&lt;/code&gt; columns into &lt;code&gt;repo:tag&lt;/code&gt; and the bare ID. &lt;code&gt;docker network ls&lt;/code&gt; and &lt;code&gt;docker volume ls&lt;/code&gt; are even simpler. The whole module is ~150 lines and unit-tested.&lt;/p&gt;

&lt;p&gt;The TUI keeps the most recent &lt;code&gt;Candidates&lt;/code&gt; in app state. When a follow-up command needs &lt;code&gt;{container}&lt;/code&gt;, it looks up &lt;code&gt;arg_kind&lt;/code&gt; for that placeholder and pulls candidates from the matching bucket. First candidate auto-fills; &lt;code&gt;↑&lt;/code&gt; / &lt;code&gt;↓&lt;/code&gt; cycle.&lt;/p&gt;

&lt;p&gt;The web UI does the same thing client-side: each &lt;code&gt;POST /api/run/:id&lt;/code&gt; returns &lt;code&gt;candidates&lt;/code&gt; alongside stdout, and the form renders a &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt; per typed arg.&lt;/p&gt;




&lt;h2&gt;
  
  
  Search across the explainers
&lt;/h2&gt;

&lt;p&gt;Every command also has a &lt;code&gt;long_desc&lt;/code&gt; — one or two sentences describing what it does and the non-obvious flags. The catalog search filters on &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;desc&lt;/code&gt;, &lt;code&gt;long_desc&lt;/code&gt;, &lt;code&gt;template&lt;/code&gt;, and &lt;code&gt;category&lt;/code&gt;, all live as you type.&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%2Ftiu7y3rhlrqgbr9jjkyx.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%2Ftiu7y3rhlrqgbr9jjkyx.gif" alt="dux search: live filtering across name, description, explainer text, and template — including matching against the explainer for kill -s SIGNAL" width="760" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The third filter in that clip — &lt;code&gt;rotation&lt;/code&gt; — only matches because the &lt;code&gt;kill -s SIGNAL&lt;/code&gt; command's explainer mentions log rotation. The explainers aren't decoration; they're a searchable index.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two surfaces from one catalog
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;main.rs&lt;/code&gt; is just clap subcommands:&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="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="py"&gt;.command&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Tui&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Tui&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;tui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;run&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;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Serve&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Runtime&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="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="nf"&gt;.block_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;web&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="nf"&gt;.parse&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;span class="n"&gt;route&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;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Catalog&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to_string_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;CATALOG&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both surfaces import the same &lt;code&gt;catalog::CATALOG&lt;/code&gt; and call the same &lt;code&gt;runner::run&lt;/code&gt; and &lt;code&gt;extract::extract&lt;/code&gt;. There's no separate "model layer." The TUI is &lt;code&gt;ratatui::Frame&lt;/code&gt; widgets; the web UI is &lt;code&gt;axum::Router&lt;/code&gt; returning &lt;code&gt;Json&amp;lt;CmdView&amp;gt;&lt;/code&gt; (where &lt;code&gt;CmdView: From&amp;lt;&amp;amp;'static Cmd&amp;gt;&lt;/code&gt;). The HTML page is a single file embedded with &lt;code&gt;include_str!("../assets/index.html")&lt;/code&gt; — no build step, no bundler.&lt;/p&gt;

&lt;p&gt;This means adding a command is one place: append to &lt;code&gt;CATALOG&lt;/code&gt;. Both UIs pick it up. Adding a new arg kind (e.g. &lt;code&gt;service&lt;/code&gt; for compose) was 4 lines in &lt;code&gt;catalog.rs&lt;/code&gt; and 3 lines in &lt;code&gt;extract.rs&lt;/code&gt;.&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%2Futjguumillnr8tv17767.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%2Futjguumillnr8tv17767.gif" alt="dux tour: browsing the 105-command catalog and running docker version" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What it isn't
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not a Docker rewrite.&lt;/strong&gt; Every command is &lt;code&gt;sh -c "docker …"&lt;/code&gt; shelled out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a dashboard.&lt;/strong&gt; There's no live state polling; the model is run-on-demand, like the CLI itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a daemon.&lt;/strong&gt; &lt;code&gt;dux serve&lt;/code&gt; is a thin HTTP-to-shell layer for your local Docker socket, intended for &lt;code&gt;localhost&lt;/code&gt; use (or behind your VPN). Don't expose it raw to the internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's also why it stays small — the binary is ~2.4 MB release-stripped and starts in ~50 ms.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;nickciolpan/tap/dux
dux                    &lt;span class="c"&gt;# TUI&lt;/span&gt;
dux serve              &lt;span class="c"&gt;# http://127.0.0.1:7878/dux&lt;/span&gt;
dux catalog | jq &lt;span class="nb"&gt;.&lt;/span&gt;     &lt;span class="c"&gt;# full command catalog as JSON&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or build from source if you'd rather:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nickciolpan/dux.git
&lt;span class="nb"&gt;cd &lt;/span&gt;dux
cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you make it interesting, the catalog is data — open a PR with a new &lt;code&gt;Cmd { … }&lt;/code&gt; entry and your follow-up suggestions, and you've extended both UIs at once.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/nickciolpan/dux" rel="noopener noreferrer"&gt;github.com/nickciolpan/dux&lt;/a&gt;&lt;br&gt;
Site: &lt;a href="https://nickciolpan.github.io/dux/" rel="noopener noreferrer"&gt;nickciolpan.github.io/dux&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>docker</category>
      <category>cli</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to Secure Claude CLI When It Runs Inside Your Software (don't ask)</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Thu, 16 Apr 2026 08:14:42 +0000</pubDate>
      <link>https://dev.to/nickciolpan/how-to-secure-claude-cli-when-it-runs-inside-your-software-dont-ask-2j2g</link>
      <guid>https://dev.to/nickciolpan/how-to-secure-claude-cli-when-it-runs-inside-your-software-dont-ask-2j2g</guid>
      <description>&lt;p&gt;If your application triggers Claude CLI server-side based on user input, you have a prompt injection surface. User types freeform text, your app wraps it in a prompt, Claude processes it. Without guardrails, that user could attempt to make Claude leak context, produce malicious output, or — if tools are enabled — interact with the host system.&lt;/p&gt;

&lt;p&gt;Five layers, stacked. None sufficient alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Text-Only Mode
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--print&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--print&lt;/code&gt; disables interactive tool use in normal operation. Claude receives text via stdin, returns text via stdout. No file reads, no bash, no writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caveat:&lt;/strong&gt; This is a behavioral constraint, not a formal security boundary. It depends on CLI implementation details and should not be your only control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: Strip Capabilities
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--print&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bare&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disallowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Bash,Edit,Write,Read,Glob,Grep,Agent,NotebookEdit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--bare&lt;/code&gt; disables hooks, LSP, plugin sync, auto-discovery of project files (&lt;code&gt;CLAUDE.md&lt;/code&gt;), and keychain reads. Reduces context available to the model — but does not guarantee zero leakage. Environment variables and OS-level information may still be accessible at the process level.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--disallowedTools&lt;/code&gt; explicitly denies every tool by name. Defense in depth — if &lt;code&gt;--print&lt;/code&gt; behavior changes in a future version, tools remain blocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Layer 3: Process Isolation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--print&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--bare&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--disallowedTools&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300000&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;&lt;code&gt;cwd: /tmp&lt;/code&gt; means the process starts in a directory with nothing interesting. This is &lt;strong&gt;not a filesystem sandbox&lt;/strong&gt; — the process can still access absolute paths. It reduces incidental exposure, not hard access.&lt;/p&gt;

&lt;p&gt;For actual isolation, run the process inside a container with restricted filesystem mounts, no network access, a non-root user, and resource limits (memory, CPU). The &lt;code&gt;cwd&lt;/code&gt; trick is a soft boundary, not a security boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: Prompt Validation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validatePrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Must contain system marker (user input alone can't form a valid prompt)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YourSystemMarker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Prompt must originate from the application&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Reduce obvious attack patterns&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sr"&gt;/``&lt;/span&gt;&lt;span class="err"&gt;`
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;(?:&lt;/span&gt;&lt;span class="nx"&gt;bash&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;sh&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;zsh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;exec&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;process&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;env/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;child_process/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;fs&lt;/span&gt;&lt;span class="se"&gt;\.\w&lt;/span&gt;&lt;span class="sr"&gt;+/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/rm&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+-rf/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/sudo&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`Blocked: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Must request structured output (app controls format, not user)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;===OUTPUT_START===&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Prompt must request delimited output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&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 system marker and output delimiters ensure the app assembled the prompt — raw user input can't pass validation alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important limitation:&lt;/strong&gt; Prompt injection is semantic, not syntactic. A user doesn't need &lt;code&gt;exec()&lt;/code&gt; or &lt;code&gt;rm -rf&lt;/code&gt; to manipulate model behavior. They can write "ignore previous instructions" or "reveal the system prompt" and no regex catches that. Pattern matching reduces surface area for obvious attacks. It does not prevent prompt injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 5: Output Containment
&lt;/h2&gt;

&lt;p&gt;This is the most important layer. Never execute Claude's output. Treat it as untrusted text.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
javascript
const match = output.match(/===OUTPUT_START===([\s\S]*?)===OUTPUT_END===/);

const targetDir = `outputs/${sessionId}/`;
fs.writeFileSync(path.join(targetDir, "result.md"), match[1]);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Write only to an isolated output directory — never source code, config, or system files&lt;/li&gt;
&lt;li&gt;Write only inert file types (markdown, static HTML) — never executable code&lt;/li&gt;
&lt;li&gt;New directory per operation — previous outputs are immutable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real danger from LLM output is &lt;strong&gt;indirect&lt;/strong&gt;: your system does something dangerous with it. If the output is never executed, evaluated, or passed to a shell, the model's text is inert regardless of what it says.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combined
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
plaintext
User input (freeform text)
  ↓
App assembles prompt (system context + delimiters + user text)
  ↓
[Layer 4] Validate prompt (origin, patterns, format)
  ↓
[Layer 1-3] claude --print --bare --disallowedTools "..." --cwd /tmp
  ↓
[Layer 5] Parse delimited output → write to isolated directory


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this achieves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User can't control prompt structure (app assembles it)&lt;/li&gt;
&lt;li&gt;Obvious injection patterns are rejected (regex filter)&lt;/li&gt;
&lt;li&gt;Tools are disabled at CLI level (behavioral + explicit deny)&lt;/li&gt;
&lt;li&gt;Host context is reduced (bare mode, /tmp cwd)&lt;/li&gt;
&lt;li&gt;Output is treated as untrusted text (never executed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What this does &lt;strong&gt;not&lt;/strong&gt; achieve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevention of semantic prompt injection ("ignore instructions")&lt;/li&gt;
&lt;li&gt;Guaranteed zero context leakage (env vars, process info)&lt;/li&gt;
&lt;li&gt;Filesystem sandboxing (cwd is not chroot)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  API vs CLI
&lt;/h2&gt;

&lt;p&gt;When calling the Anthropic API directly, layers 1-3 don't apply — there's no CLI process. Layers 4 and 5 still work identically. The API has no filesystem access by default, but injection risk remains: the model can still be manipulated to leak data you included in the prompt or produce output that influences downstream systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting Point, Not Endpoint
&lt;/h2&gt;

&lt;p&gt;With all five layers applied, Claude is rendered effectively harmless — it can't use tools, can't see files, can't execute commands, and its output goes nowhere dangerous. This is the correct starting point. Strip everything, verify it's inert, then selectively grant back capability and access as your use case requires — with each addition evaluated as a new attack surface.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>backend</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>Your Dockerfile Scanner Should Break the Build</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Wed, 25 Mar 2026 07:33:28 +0000</pubDate>
      <link>https://dev.to/nickciolpan/your-dockerfile-scanner-should-break-the-build-4b9k</link>
      <guid>https://dev.to/nickciolpan/your-dockerfile-scanner-should-break-the-build-4b9k</guid>
      <description>&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;Last month I shipped &lt;a href="https://github.com/nickciolpan/docker-scan-lite" rel="noopener noreferrer"&gt;docker-scan-lite&lt;/a&gt;. It scanned. It warned.&lt;/p&gt;

&lt;p&gt;Then everyone kept shipping broken images anyway.&lt;/p&gt;

&lt;p&gt;Because it always exited &lt;code&gt;0&lt;/code&gt;. Green pipeline. Every time. Didn't matter if you had &lt;code&gt;USER root&lt;/code&gt; with a hardcoded AWS key. CI said ✅. You shipped it.&lt;/p&gt;

&lt;p&gt;Warnings without consequences are just noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Now it breaks the build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-scan-lite &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile &lt;span class="nt"&gt;--exit-code&lt;/span&gt; high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One flag. Pipeline stops when it matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Action
&lt;/h3&gt;

&lt;p&gt;No install step. No binary downloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan Dockerfile&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nickciolpan/docker-scan-lite@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;fail-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hardcoded secret? Blocked.&lt;br&gt;
Running as root? Blocked.&lt;br&gt;
Sensitive env var in plaintext? Blocked.&lt;/p&gt;

&lt;p&gt;Everything else — warnings. You see them, you decide.&lt;/p&gt;
&lt;h3&gt;
  
  
  New checks
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Missing HEALTHCHECK:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️ [INFO] No HEALTHCHECK instruction found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your orchestrator is flying blind without it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No USER instruction:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️ [MEDIUM] No USER instruction in final stage. Container will run as root by default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not &lt;code&gt;USER root&lt;/code&gt; — &lt;em&gt;no USER at all&lt;/em&gt;. The silent default nobody thinks about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-stage awareness:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder    # issues here matter less&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; /app

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.18               # this is what ships&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app /app&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It now knows the difference between a build stage and what actually runs in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Less noise
&lt;/h3&gt;

&lt;p&gt;Before, every URL got flagged as a "database connection string":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️ database_url: https://example.com/install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixed. Only actual DB protocols now — &lt;code&gt;postgres://&lt;/code&gt;, &lt;code&gt;mysql://&lt;/code&gt;, &lt;code&gt;mongodb://&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FROM scratch&lt;/code&gt; no longer gets flagged as "using latest tag". It's not an image.&lt;/p&gt;

&lt;p&gt;Want only the critical stuff?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-scan-lite &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile &lt;span class="nt"&gt;--severity&lt;/span&gt; high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SARIF output
&lt;/h3&gt;

&lt;p&gt;For GitHub's Security tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nickciolpan/docker-scan-lite@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sarif&lt;/span&gt;
    &lt;span class="na"&gt;fail-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload SARIF&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scan-results.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dockerfile issues show up next to your CodeQL findings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew upgrade nickciolpan/tap/docker-scan-lite
&lt;span class="c"&gt;# or&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/nickciolpan/docker-scan-lite@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://nickciolpan.github.io/docker-scan-lite" rel="noopener noreferrer"&gt;nickciolpan.github.io/docker-scan-lite&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/nickciolpan/docker-scan-lite" rel="noopener noreferrer"&gt;github.com/nickciolpan/docker-scan-lite&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;What's the worst thing your CI let through?&lt;/p&gt;




&lt;p&gt;Yes, this was written with the help of an LLM. The code too. Are we still pretending that's not how things get built in 2026? Claude wrote most of the implementation, I steered, tested, broke things, and made the decisions.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>security</category>
      <category>github</category>
    </item>
    <item>
      <title>I built a terminal-native Little Snitch alternative for macOS</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Tue, 24 Mar 2026 11:49:08 +0000</pubDate>
      <link>https://dev.to/nickciolpan/i-built-a-terminal-native-little-snitch-alternative-for-macos-4807</link>
      <guid>https://dev.to/nickciolpan/i-built-a-terminal-native-little-snitch-alternative-for-macos-4807</guid>
      <description>&lt;p&gt;I wanted to know what my Mac was doing behind my back.&lt;/p&gt;

&lt;p&gt;Every app phones home. Electron apps ping telemetry endpoints. Browsers hit trackers. Even system processes make connections you never asked for. Little Snitch shows you all of this beautifully — it's a proper, polished product and well worth the money.&lt;/p&gt;

&lt;p&gt;This is not a replacement for Little Snitch. This is what happens when you're curious about network monitoring and want to see how far you can get with Go, &lt;code&gt;lsof&lt;/code&gt;, and &lt;code&gt;pfctl&lt;/code&gt; in a terminal. Think of it as a learning project that accidentally became useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI Snitch&lt;/strong&gt; watches every outbound TCP and UDP connection, prompts you to allow or deny, and enforces your decisions with real macOS firewall rules — all from the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;cli-snitch watch
&lt;span class="go"&gt;
🚨 New Outbound Connection Detected
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  📱 Application: Electron Helper
  🌐 Destination: telemetry.example.com:443
  🔌 Protocol:    TCP
  🏷️  Host Info:   Amazon Web Services (AWS)

? What would you like to do?
  ✅ Allow Once
  🔁 Allow Always
  ❌ Deny Once
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;🚫 Deny Always
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;❌ Electron Helper DENIED -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;telemetry.example.com:443
&lt;span class="go"&gt;🔥 Firewall rule: block out proto tcp from any to telemetry.example.com port 443
🛡️ Firewall rule applied
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is real. It's not a log message — it's an actual &lt;code&gt;pfctl&lt;/code&gt; rule injected into the macOS packet filter. The connection is blocked at the kernel level.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The architecture is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Detect&lt;/strong&gt; — &lt;code&gt;lsof -i tcp -i udp -n&lt;/code&gt; every 2 seconds (adaptive intervals)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match&lt;/strong&gt; — Check new connections against saved rules (case-insensitive, supports glob patterns)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt&lt;/strong&gt; — If no rule matches, ask the user via an interactive terminal prompt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce&lt;/strong&gt; — Deny decisions create &lt;code&gt;pfctl&lt;/code&gt; anchor rules that survive the session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remember&lt;/strong&gt; — Decisions are saved as JSON rules and logged to a JSONL history file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting engineering problems were:&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt serialization
&lt;/h3&gt;

&lt;p&gt;Multiple connections can arrive within the same 2-second scan. If two prompts hit stdin simultaneously, everything breaks. I solved this with a buffered channel queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;promptRequest&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;     &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;
    &lt;span class="n"&gt;resultCh&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;promptResult&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ConnectionPrompter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;QueuePrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserDecision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;promptRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;resultCh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;promptResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;promptQueue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resultCh&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single goroutine reads from the queue and calls the survey prompt — one at a time, no collisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  pfctl input sanitization
&lt;/h3&gt;

&lt;p&gt;When a user denies a connection, the host and port values end up in a pfctl command. If I naively concatenated those strings, a malformed hostname could inject commands. Every value is validated against regex before it touches pfctl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hostPattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`^[a-zA-Z0-9._:\-]+$`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;portPattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`^[0-9]+$`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connection deduplication
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;lsof&lt;/code&gt; reports &lt;em&gt;all&lt;/em&gt; active connections every scan, not just new ones. The monitor maintains a map of seen connections plus a TTL-based "recent cache" — so connections that get cleaned up from the main map but reappear within 5 minutes don't re-trigger prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;14 commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;watch              Real-time monitoring (the main event)
list-rules         See all your allow/deny rules
edit-rule          Modify a rule inline
import-rules       Load rules from JSON
export-rules       Back up rules to JSON
history            View connection log with filters
firewall-status    Check pfctl integration
list-firewall      Show active blocking rules
clear-firewall     Remove all pfctl rules
firewall-cleanup   Remove expired temp rules
firewall-monitor   Live firewall status
system-status      Full diagnostics
daemon install     Set up as a launchd service
daemon start/stop  Control the background service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some features I'm particularly happy with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wildcard rules&lt;/strong&gt; — &lt;code&gt;*.analytics.com&lt;/code&gt; blocks all analytics subdomains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS reverse lookup&lt;/strong&gt; with caching — so you see hostnames, not just IPs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection history&lt;/strong&gt; — every decision logged to JSONL, filterable by process or action&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daemon mode&lt;/strong&gt; — &lt;code&gt;sudo cli-snitch daemon install&lt;/code&gt; creates a launchd plist for background monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule scoping&lt;/strong&gt; — block a specific connection, all connections to a host, all connections on a port, or everything from a process&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap nickciolpan/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;cli-snitch
&lt;span class="nb"&gt;sudo &lt;/span&gt;cli-snitch watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or build from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nickciolpan/snitcher
&lt;span class="nb"&gt;cd &lt;/span&gt;snitcher
go build &lt;span class="nt"&gt;-o&lt;/span&gt; cli-snitch ./cmd/cli-snitch
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./cli-snitch watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it can't do
&lt;/h2&gt;

&lt;p&gt;Being honest about the limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No per-process blocking in pfctl&lt;/strong&gt; — macOS packet filter doesn't have a concept of process ownership. If two apps connect to the same host:port, a deny rule blocks both. True per-process filtering needs Apple's Network Extension framework (Swift, not Go).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No bandwidth tracking&lt;/strong&gt; — would need BPF packet capture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lsof truncates process names&lt;/strong&gt; — &lt;code&gt;Google Chrome Helper&lt;/code&gt; becomes &lt;code&gt;Google&lt;/code&gt;, &lt;code&gt;Slack&lt;/code&gt; becomes &lt;code&gt;Slack\x20&lt;/code&gt;. Works fine once you know the quirk.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;lsof is surprisingly good&lt;/strong&gt; for this use case. It's fast, available everywhere, and the output is parseable. The main gotcha is IPv6 bracket notation and process name encoding.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;pfctl anchors are the right abstraction.&lt;/strong&gt; By isolating all CLI Snitch rules in a named anchor, there's zero risk of clobbering system firewall rules. Cleanup is just "reload an empty anchor file."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interactive CLI tools need careful goroutine design.&lt;/strong&gt; The prompt queue pattern — buffered channel + single-reader goroutine — is something I'll reuse in every interactive CLI from now on.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The source is at &lt;a href="https://github.com/nickciolpan/snitcher" rel="noopener noreferrer"&gt;github.com/nickciolpan/snitcher&lt;/a&gt; and the docs are at &lt;a href="https://cli-snitch.ciolpan.com" rel="noopener noreferrer"&gt;cli-snitch.ciolpan.com&lt;/a&gt;. MIT licensed.&lt;/p&gt;

&lt;p&gt;If you've ever wondered what your Mac is doing when you're not looking, give it a try. You might be surprised.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Yes, this was written with the help of an LLM. The code too. Are we still pretending that's not how things get built in 2026? Claude wrote most of the implementation, I steered, tested, broke things, and made the decisions. The architecture is real, the bugs were real, and the pfctl rules definitely blocked my Chrome tabs for real. Tools are tools — what matters is whether the thing works. It does.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>macos</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Stop shipping insecure file permissions</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Mon, 23 Jun 2025 10:55:09 +0000</pubDate>
      <link>https://dev.to/nickciolpan/stop-shipping-insecure-file-permissions-44mp</link>
      <guid>https://dev.to/nickciolpan/stop-shipping-insecure-file-permissions-44mp</guid>
      <description>&lt;p&gt;We set up file permissions in a hurry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chmod 777&lt;/code&gt; (it just works)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chmod 666&lt;/code&gt; (for testing)&lt;/li&gt;
&lt;li&gt;No SUID audit (it's just one binary)&lt;/li&gt;
&lt;li&gt;Open temp files (gotta ship)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We hear warnings but keep driving.&lt;/p&gt;

&lt;h1&gt;
  
  
  DO:
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -L https://github.com/nickciolpan/permcheck/releases/latest/download/permcheck-linux-amd64 -o permcheck
chmod +x permcheck
sudo mv permcheck /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Catches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;World-writable files&lt;/li&gt;
&lt;li&gt;SUID/SGID binaries&lt;/li&gt;
&lt;li&gt;Insecure temp files&lt;/li&gt;
&lt;li&gt;Overly permissive directories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╔══════════════════════════════════════════════════════════════════╗
║                    🔒 SECURITY SCAN INITIATED                   ║
╚══════════════════════════════════════════════════════════════════╝

🌍 WORLD-WRITABLE FILES (2 found):
  ⚠️  /home/user/project/config.txt (0666)
      💡 World-writable means ANY user can modify this file!
      💡 Consider: chmod 644 /home/user/project/config.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup (30 seconds)
&lt;/h2&gt;

&lt;p&gt;Add to CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Security Scan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;permcheck scan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;30 seconds to install. Catches stupid mistakes before production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Executables&lt;/strong&gt;: &lt;code&gt;755&lt;/code&gt; (rwxr-xr-x)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration&lt;/strong&gt;: &lt;code&gt;644&lt;/code&gt; (rw-r--r--)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive data&lt;/strong&gt;: &lt;code&gt;600&lt;/code&gt; (rw-------)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directories&lt;/strong&gt;: &lt;code&gt;755&lt;/code&gt; (rwxr-xr-x)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;777&lt;/code&gt; (rwxrwxrwx)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;666&lt;/code&gt; (rw-rw-rw-)&lt;/li&gt;
&lt;li&gt;Any world-writable permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/nickciolpan/permcheck" rel="noopener noreferrer"&gt;https://github.com/nickciolpan/permcheck&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's your worst file permission mistake?&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>unix</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Shipping Broken Docker Images</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Wed, 11 Jun 2025 12:43:51 +0000</pubDate>
      <link>https://dev.to/nickciolpan/stop-shipping-broken-docker-images-19d1</link>
      <guid>https://dev.to/nickciolpan/stop-shipping-broken-docker-images-19d1</guid>
      <description>&lt;p&gt;docker-scan-lite after seeing too many prod incidents from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FROM ubuntu:latest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;USER root&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Hardcoded API keys&lt;/li&gt;
&lt;li&gt;No version pinning&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  DON'T
&lt;/h2&gt;

&lt;p&gt;Last week: seen container compromised because someone deployed with root user + hardcoded API key.&lt;/p&gt;

&lt;p&gt;This happens. We write Dockerfiles in a hurry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM ubuntu:latest&lt;/code&gt; (no time for versions)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;USER root&lt;/code&gt; (it just works)&lt;/li&gt;
&lt;li&gt;Hardcoded secrets (just for testing)&lt;/li&gt;
&lt;li&gt;No pinning (build's failing, gotta ship)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we hear warnings but keep driving.&lt;/p&gt;

&lt;h2&gt;
  
  
  DO
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/nickciolpan/docker-scan-lite@latest
docker-scan-lite scan Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Catches:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vulnerable base images&lt;/li&gt;
&lt;li&gt;Hardcoded secrets
&lt;/li&gt;
&lt;li&gt;Root user configs&lt;/li&gt;
&lt;li&gt;Insecure commands&lt;/li&gt;
&lt;li&gt;Unpinned packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🐳 Docker Scan Lite Results
📊 Summary: 3 issues found
🔒 Security Issues
  ⚠️ [HIGH] Container running as root user (line 15)
  ⚠️ [MEDIUM] Using 'latest' tag not recommended (line 1)
  ⚠️ [LOW] Package installation without version pinning (line 8)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup (30 seconds)
&lt;/h2&gt;

&lt;p&gt;Add to CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan Dockerfile&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker-scan-lite scan Dockerfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;30 seconds to install. Catches stupid mistakes before production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/nickciolpan/docker-scan-lite" rel="noopener noreferrer"&gt;https://github.com/nickciolpan/docker-scan-lite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's your worst Docker security mistake?&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker #DevOps #Security
&lt;/h1&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>TIL How to Batch Compress PDF Files Using Ghostscript</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Sun, 06 Oct 2024 08:31:46 +0000</pubDate>
      <link>https://dev.to/nickciolpan/til-how-to-batch-compress-pdf-files-using-ghostscript-5ejg</link>
      <guid>https://dev.to/nickciolpan/til-how-to-batch-compress-pdf-files-using-ghostscript-5ejg</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install ghostscript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
[ $# -lt 3 ] &amp;amp;&amp;amp; { echo "Usage: $0 /input_dir /output_dir /quality"; exit 1; }
input_dir="$1"; output_dir="$2"; quality="$3"
mkdir -p "$output_dir"
for file in "$input_dir"/*.pdf; do
  base=$(basename "$file" .pdf)
  gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS="$quality" -dNOPAUSE -dQUIET -dBATCH -sOutputFile="$output_dir/${base}_compressed.pdf" "$file"
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./compress_pdfs.sh /path/to/input /path/to/output /quality_setting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quality options: /screen, /ebook, /printer, /prepress.&lt;/p&gt;

&lt;p&gt;Say goodbye to online pdf converters. &lt;/p&gt;

&lt;p&gt;Originally posted on: &lt;a href="https://graffino.com/til/til-how-to-batch-compress-pdf-files-using-ghostscript" rel="noopener noreferrer"&gt;https://graffino.com/til/til-how-to-batch-compress-pdf-files-using-ghostscript&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>TIL: How to Quickly Check Which AWS Regions Support SES Using a Bash Script</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Sun, 06 Oct 2024 05:53:00 +0000</pubDate>
      <link>https://dev.to/nickciolpan/til-how-to-quickly-check-which-aws-regions-support-ses-using-a-bash-script-262g</link>
      <guid>https://dev.to/nickciolpan/til-how-to-quickly-check-which-aws-regions-support-ses-using-a-bash-script-262g</guid>
      <description>&lt;p&gt;Although it has improved lately for some services, AWS is still notoriously unfriendly when it comes to checking which services are activated and running across different regions. Here's a script you can run in the console to quickly see which regions have SES (Simple Email Service) available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
for region in $(aws ec2 describe-regions --query "Regions[].RegionName" --output text); do
    echo "Checking SES in region: $region"
    if output=$(aws ses get-send-quota --region $region 2&amp;gt;&amp;amp;1); then
        echo "SES is active in region: $region"
        echo "$output"
    else
        echo "SES is not available in region: $region"
    fi
    echo "---------------------------------------"
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;This script loops through all AWS regions and checks if SES is running in each one, giving you a quick and easy overview of SES availability.&lt;/p&gt;

&lt;p&gt;The output: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
Checking SES in region: us-east-1&lt;br&gt;
SES is active in region: us-east-1&lt;br&gt;
{&lt;br&gt;
    "Max24HourSend": 50000.0,&lt;br&gt;
    "MaxSendRate": 14.0,&lt;br&gt;
    "SentLast24Hours": 0.0&lt;/p&gt;

&lt;h2&gt;
  
  
  }
&lt;/h2&gt;

&lt;p&gt;Checking SES in region: us-west-1&lt;/p&gt;

&lt;h2&gt;
  
  
  SES is not available in region: us-west-1
&lt;/h2&gt;

&lt;p&gt;Checking SES in region: eu-west-1&lt;br&gt;
SES is active in region: eu-west-1&lt;br&gt;
{&lt;br&gt;
    "Max24HourSend": 50000.0,&lt;br&gt;
    "MaxSendRate": 10.0,&lt;br&gt;
    "SentLast24Hours": 0.0&lt;/p&gt;

&lt;h2&gt;
  
  
  }
&lt;/h2&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;First appeared at: &lt;a href="https://graffino.com/til/til-how-to-quickly-check-which-aws-regions-support-ses-using-a-bash-script" rel="noopener noreferrer"&gt;https://graffino.com/til/til-how-to-quickly-check-which-aws-regions-support-ses-using-a-bash-script&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Orthogonality in Programming: Why Elm Gets It Right</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Wed, 11 Sep 2024 07:41:41 +0000</pubDate>
      <link>https://dev.to/nickciolpan/orthogonality-in-programming-why-elm-gets-it-right-259p</link>
      <guid>https://dev.to/nickciolpan/orthogonality-in-programming-why-elm-gets-it-right-259p</guid>
      <description>&lt;p&gt;The debate over the perfect language syntax is far from over—in fact, it’s &lt;strong&gt;constantly diverging&lt;/strong&gt;. And I believe the mainstream is heading in the wrong direction.&lt;/p&gt;

&lt;p&gt;When evaluating a language, we typically look at factors like expressiveness, readability, abstraction, and consistency—familiar metrics.&lt;/p&gt;

&lt;p&gt;JavaScript, on its own, isn’t perfect, but it’s not terrible either.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;real issue&lt;/strong&gt; isn’t just JavaScript—it’s the &lt;strong&gt;language of the web&lt;/strong&gt;. To deliver a complete web experience, you need to juggle multiple languages and syntaxes and up with a chimera. Modern tooling leads to something like this (React devs will know this well):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
    &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; 
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;{{...}}&lt;/span&gt; 
    &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="s"&gt;{()=&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...} 
    data-testid="..."&amp;gt;
    {....}
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to cover all the constructs that can be expressed in a web interface, the language expands its domain to cover all possible meaning and interaction. This is the equivalent of writing 100 branching &lt;code&gt;if-else&lt;/code&gt; statements to account for every condition. &lt;/p&gt;

&lt;p&gt;But if you’ve grown up with this syntax, you’re likely blind to it—and that’s completely normal.&lt;/p&gt;

&lt;p&gt;What’s missing is &lt;strong&gt;orthogonality&lt;/strong&gt;— being the ability to express a large set of concepts with a small set of constructs.&lt;/p&gt;

&lt;p&gt;Now, Elm gets it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elm"&gt;&lt;code&gt;&lt;span class="n"&gt;div&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;color&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;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-testid"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&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;&lt;strong&gt;In Elm, everything revolves around functions and lists—lists of values, lists of functions, and functions that operate on lists.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Why is this important? Combined with type safety, it provides both &lt;strong&gt;predictability&lt;/strong&gt; AND &lt;strong&gt;flexibility&lt;/strong&gt;—two concepts that may seem contradictory to the uninitiated.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Beyond workarounds: DCI offers a genuine take on traditional OOP design patterns</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Wed, 15 Nov 2023 08:50:19 +0000</pubDate>
      <link>https://dev.to/nickciolpan/beyond-workarounds-dci-offers-a-genuine-take-on-traditional-oop-design-patterns-3pjd</link>
      <guid>https://dev.to/nickciolpan/beyond-workarounds-dci-offers-a-genuine-take-on-traditional-oop-design-patterns-3pjd</guid>
      <description>&lt;p&gt;Object-Oriented Programming (OOP) design patterns are established solutions to common problems in software design. However, upon closer inspection, one might consider them workarounds operating within the constraints of the traditional OOP paradigm. This paradigm is, in fact, class-oriented programming with a capital 'C'. It focuses on objects as entities that combine state and behavior, reasoning about the "real world" as categories and hierarchies, rather than as networks of collaborating objects, which was Alan Kay's original vision.&lt;/p&gt;

&lt;p&gt;This brief compilation serves as a reference and a slightly more extended response to the word count-restricted comment on this LinkedIn article: &lt;a href="https://www.linkedin.com/advice/3/how-can-you-design-structured-programs-easy-read-maintain-hhkcc?trk=cah2"&gt;https://www.linkedin.com/advice/3/how-can-you-design-structured-programs-easy-read-maintain-hhkcc?trk=cah2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DCI (Data-Context-Interaction) aims to address some of the limitations of OOP by introducing a paradigm where the system's data and its behavior are treated as separate concerns. Here's how DCI can be seen as a radical solution that potentially eliminates the need for certain OOP design patterns:&lt;/p&gt;

&lt;p&gt;Role Separation: In traditional OOP, an object's role is often inferred from its class. DCI, however, makes roles explicit. Roles in DCI are about what an object does in a given context, not what it is forever bound to by its class. This can reduce the need for patterns that deal with behavior variations, like Strategy or State patterns.&lt;/p&gt;

&lt;p&gt;Behavior Encapsulation: OOP typically encapsulates behavior within objects. DCI encapsulates behavior within contexts, making the code more readable and aligned with the actual use cases, which can diminish the reliance on patterns like Command or Template Method that are used to manage behaviors.&lt;/p&gt;

&lt;p&gt;Contexts Over Classes: Many OOP design patterns (e.g., Observer, Mediator) are used to handle interactions between classes. DCI promotes the use of contexts to manage interactions, which can simplify complex communication patterns and reduce the need for intermediary objects or class hierarchies.&lt;/p&gt;

&lt;p&gt;Interactions as First-Class Citizens: In DCI, interactions are not second-class citizens that just emerge from objects calling each other's methods. They are first-class citizens that can be directly modeled and manipulated, which could negate the need for patterns that orchestrate interactions like Chain of Responsibility or Mediator.&lt;/p&gt;

&lt;p&gt;Object Adaptation: DCI allows objects to take on different roles in different contexts, which can alleviate the need for patterns like Adapter or Decorator that are traditionally used to modify an object's interface or add new responsibilities.&lt;/p&gt;

&lt;p&gt;Human Mental Models: DCI is designed to reflect human mental models more accurately than traditional OOP. It models the system based on real-world scenarios, potentially reducing the need for patterns that exist primarily to bridge the gap between human thinking and system design, such as Facade or Builder.&lt;/p&gt;

&lt;p&gt;DCI proposes a radical shift in thinking about object interactions, aiming to make the codebase more intuitive and better aligned with the problem domain. However, it's important to understand both the context in which design patterns are used and the context in which DCI can be applied, to make the best use of each according to the needs of the project.&lt;/p&gt;

</description>
      <category>dci</category>
      <category>designpatterns</category>
      <category>oop</category>
      <category>programming</category>
    </item>
    <item>
      <title>Beyond the Hype: Rethinking Decoupled Architecture and the Pursuit of Modern Frontends</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Wed, 04 Oct 2023 09:28:03 +0000</pubDate>
      <link>https://dev.to/nickciolpan/beyond-the-hype-rethinking-decoupled-architecture-and-the-pursuit-of-modern-frontends-3hhe</link>
      <guid>https://dev.to/nickciolpan/beyond-the-hype-rethinking-decoupled-architecture-and-the-pursuit-of-modern-frontends-3hhe</guid>
      <description>&lt;p&gt;Decoupled architecture used to be the magic bullet when one generator (data source) catered to multiple consumers (multiple frontends, third-party services). But that narrative has shifted. Nowadays, it's often seen as the key to leveraging React.&lt;/p&gt;

&lt;p&gt;Chasing after the most popular frameworks can sometimes lead us astray. While it might look appealing on job listings, introducing a new layer, like a “beautiful” RESTful JSON API, comes with its own set of complications. Think JWT authentication, state management, data serialization, and API versioning. Remember, these are strategies to mitigate problems, not features!&lt;/p&gt;

&lt;p&gt;I recall a conversation where I cautioned against mindlessly using "react-create-new-app" for every new project. The retort was, "What's the alternative? It was worse before with templates." We mustn't be trapped by the past. It's crucial to keep an eye on the horizon.&lt;/p&gt;

&lt;p&gt;Single Page Applications (SPAs) have indeed revolutionized front-end development, introducing many best practices. Let's delve into a few:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Isomorphism:&lt;/strong&gt; A JavaScript boon. First, JavaScript runs on the server, then again on the client. Frameworks like meteor.js elevate this by offering data on-the-fly through live databases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-driven UI:&lt;/strong&gt; Take Livewire or Blazor as examples. As Alan Kay once said, "For all it matters, for the user, the interface is the end product." Such tools allow developers to craft end-to-end apps in a contemporary fashion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adapters and Glue:&lt;/strong&gt; Consider Interia.js or Hilla for Java. The latter seamlessly translates Java types and resources into TypeScript types. It even supports calling Java services from a React component without any extra setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In sum, while JSP pages and old index.php files might have their detractors, there are innovative solutions available that don't necessitate maintaining an extra layer just for the pleasure of using React.&lt;/p&gt;

&lt;p&gt;What about future mobile app needs? Unless it's specified by the client, are you factoring in the costs of your personal assumptions?&lt;/p&gt;

&lt;p&gt;Let's conclude with a parable: A young boy asked his mother why she chopped off the ends of the corn before boiling it. She didn’t know, saying it was a method learned from her own mother. The grandmother similarly pointed to her mother. The great-grandmother, when questioned, revealed she had a small pot, necessitating the corn’s truncation. Three generations persisted with an unnecessary practice, oblivious to its origins.&lt;/p&gt;

&lt;p&gt;This story underlines a key point: don't blindly follow industry trends. Understand the compromises and context behind every choice. Before jumping into modern frontend, question if there's a smarter approach that sidesteps the pitfalls, putting you firmly in the driver's seat of solution architecture.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Enhancing Models with Meta Attributes: A Dive into Business Logic Beyond the Database</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Wed, 04 Oct 2023 09:26:12 +0000</pubDate>
      <link>https://dev.to/nickciolpan/enhancing-models-with-meta-attributes-a-dive-into-business-logic-beyond-the-database-4bb0</link>
      <guid>https://dev.to/nickciolpan/enhancing-models-with-meta-attributes-a-dive-into-business-logic-beyond-the-database-4bb0</guid>
      <description>&lt;p&gt;A perfect example of this complexity is when certain fields in your models require additional metadata - a set of attributes that aren't stored directly in the database but are instead derived or computed from existing fields. This metadata often encapsulates business rules and logic that can be tailored to individual model instances.&lt;/p&gt;

&lt;p&gt;Web applications, especially those built on top of relational databases, often lean heavily on their models to define and structure their data. These models are typically a direct reflection of the database tables they represent. However, in the complex world of modern web development, sometimes the data in the database isn't enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Carrying Business Logic with Meta Attributes
&lt;/h2&gt;

&lt;p&gt;Consider an eCommerce application where each product has a price. This price, though a singular value in the database, might carry with it a wealth of additional information such as currency, tax percentage, discount applicability, and more. Rather than altering the database schema to accommodate these attributes, they can be computed on the fly based on business rules, thus preserving database simplicity while providing enriched data to the application.&lt;/p&gt;

&lt;p&gt;Let's dive into this with a concrete example.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;HasMetaAttributes&lt;/code&gt; Trait
&lt;/h3&gt;

&lt;p&gt;This PHP trait, designed to be used within a Laravel application, empowers any Eloquent model to handle meta attributes seamlessly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;Traits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;Illuminate&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;Support&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;trait&lt;/span&gt; &lt;span class="nx"&gt;HasMetaAttributes&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;$globalWithMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;$instanceWithMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;toggleMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bool&lt;/span&gt; &lt;span class="nx"&gt;$withMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;$globalWithMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$withMeta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;withMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bool&lt;/span&gt; &lt;span class="nx"&gt;$withMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;instanceWithMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$withMeta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;$withMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;instanceWithMeta&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;instanceWithMeta&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;$globalWithMeta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$withMeta&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;$meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;$meta&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;$attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;$withMeta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;instanceWithMeta&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;instanceWithMeta&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;$globalWithMeta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$withMeta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;$metaData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array_key_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta&lt;/span&gt;&lt;span class="dl"&gt;'&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;span class="nx"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;$metaData&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="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;$attributes&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When used in a model, the trait offers the capability to toggle the meta attributes on or off, either globally across all model instances or on a per-instance basis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application in an Eloquent Model
&lt;/h3&gt;

&lt;p&gt;For the sake of illustration, we will use a &lt;code&gt;Product&lt;/code&gt; model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="nc"&gt;Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="nc"&gt;Eloquent&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="nc"&gt;Traits&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="nc"&gt;HasMetaAttributes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HasMetaAttributes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$fillable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'currency'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'USD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'tax'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'10%'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;Models&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;toggleMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="nx"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;100.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tax&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10%&lt;/span&gt;&lt;span class="dl"&gt;"&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;Toggle Off&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;toggleMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;echo&lt;/span&gt; &lt;span class="nx"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="mf"&gt;100.00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architectural Considerations and Alternative Approaches
&lt;/h2&gt;

&lt;p&gt;The approach detailed above certainly offers a layer of dynamism to your models. However, depending on the application's complexity and performance needs, some might find alternative designs more fitting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Database Views&lt;/strong&gt;: Instead of computing meta attributes in the application, create a database view that joins and aggregates data as required. This will offload computational tasks to the database but might make the application logic less clear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Layer&lt;/strong&gt;: Rather than enriching models directly, introduce a service layer that fetches model data and then augments it with additional attributes. This separates business logic from data representation and is often a favored approach in complex applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decorator Pattern&lt;/strong&gt;: Use a decorator to wrap around the model, adding additional behavior or data without modifying the model's structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Listeners&lt;/strong&gt;: In scenarios where computed attributes don't change frequently, compute them once and store the results. Use event listeners to re-compute whenever the underlying data changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Models, while traditionally reflecting database structures, can be enhanced to carry complex business logic rules that go beyond the scope of the database. The &lt;code&gt;HasMetaAttributes&lt;/code&gt; trait provides an elegant way of achieving this in a Laravel application, though alternative architectural patterns might offer other advantages based on specific requirements.&lt;/p&gt;

&lt;p&gt;As developers, we must remember that there isn't a one-size-fits-all solution. The best approach always depends on the problem at hand, the current architecture, and future scalability needs. Whichever path you choose, make sure it aligns with both the short-term and long-term goals of your application.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
