<?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>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>
    <item>
      <title>Enhancing Laravel Commands: Parameterized Inputs and Custom Output Formats [4/4]</title>
      <dc:creator>Nick Ciolpan</dc:creator>
      <pubDate>Fri, 26 May 2023 13:02:17 +0000</pubDate>
      <link>https://dev.to/graffino/enhancing-laravel-commands-parameterized-inputs-and-custom-output-formats-4ebl</link>
      <guid>https://dev.to/graffino/enhancing-laravel-commands-parameterized-inputs-and-custom-output-formats-4ebl</guid>
      <description>&lt;p&gt;So far, so good. It gives us some insights into the current state of our gating system, but not much more. Since we've come this far, we might as well take one step further and incorporate two crucial aspects of commands: the ability to pass parameters. Once we have this functionality in place, the console kernel becomes equally powerful as the web kernel, granting access to all infrastructure and data layers, just as you would normally have.&lt;/p&gt;

&lt;p&gt;What we do is pass a username to the command and then examine each gate for the retrieved user. We mark, in an additional column, whether the user can pass it or not, indicating a green "pass" or a red "restricted".&lt;/p&gt;

&lt;p&gt;Try to and identify the new code additions. I’ll give you a hint: start with the command signature. Feel free to comment if you have any questions. And, most importantly, have fun!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Contracts\Auth\Access\Gate;
use App\Models\User;

class CheckGates extends Command
{
    protected $signature = 'gate:check {username}';
    protected $description = 'Check access of user for all defined gates in Laravel';

    protected $gate;

    public function __construct(Gate $gate)
    {
        parent::__construct();
        $this-&amp;gt;gate = $gate;
    }
    protected function getClosureContents(\Closure $closure)
    {
        $reflection = new \ReflectionFunction($closure);
        $closureCode = file($reflection-&amp;gt;getFileName());

        $startLine = $reflection-&amp;gt;getStartLine();
        $endLine = $reflection-&amp;gt;getEndLine();

        $contents = array_slice($closureCode, $startLine - 1, $endLine - $startLine + 1);

        return implode('', $contents);
    }
    public function handle()
    {
        $username = $this-&amp;gt;argument('username');
        $user = User::where('username', $username)-&amp;gt;first();

        if (!$user) {
            $this-&amp;gt;error("No user found with username: $username");
            return;
        }

        $gates = $this-&amp;gt;gate-&amp;gt;abilities();
        $self = $this;

        $tableData = array_reduce(array_keys($gates), function ($carry, $key) use ($gates, $user, $self) {
            $value = $gates[$key];
            $hasAccess = $this-&amp;gt;gate-&amp;gt;forUser($user)-&amp;gt;check($key);

            $carry[] = [
                'Gate Name' =&amp;gt; $key,
                'Access' =&amp;gt; $hasAccess ? '&amp;lt;fg=green&amp;gt;Pass&amp;lt;/&amp;gt;' : '&amp;lt;fg=red&amp;gt;Restricted&amp;lt;/&amp;gt;',
                'Closure Contents' =&amp;gt; $self-&amp;gt;getClosureContents($value),
            ];

            return $carry;
        }, []);

        $this-&amp;gt;table(['Gate Name','Access', 'Closure Contents'], $tableData);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>php</category>
      <category>laravel</category>
      <category>codenewbie</category>
    </item>
  </channel>
</rss>
