<?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: Jonne Riepponen</title>
    <description>The latest articles on DEV Community by Jonne Riepponen (@jronegit).</description>
    <link>https://dev.to/jronegit</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4163710%2F26ffbc3d-0094-44d1-9f8b-30737c1f6aa4.png</url>
      <title>DEV Community: Jonne Riepponen</title>
      <link>https://dev.to/jronegit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jronegit"/>
    <language>en</language>
    <item>
      <title>I Built a Password Strength Checker That Runs 100% in Your Browser (and What It Taught Me About Passwords)</title>
      <dc:creator>Jonne Riepponen</dc:creator>
      <pubDate>Wed, 07 Oct 2026 15:38:39 +0000</pubDate>
      <link>https://dev.to/jronegit/i-built-a-password-strength-checker-that-runs-100-in-your-browser-and-what-it-taught-me-about-2oka</link>
      <guid>https://dev.to/jronegit/i-built-a-password-strength-checker-that-runs-100-in-your-browser-and-what-it-taught-me-about-2oka</guid>
      <description>&lt;h2&gt;
  
  
  The Phone Call That Started This
&lt;/h2&gt;

&lt;p&gt;A client's junior admin called me last month. Their company had just rolled out a new password policy — 14 characters, three character classes, rotation every 90 days — and the helpdesk was drowning in reset tickets. The admin was proud of the policy. The users were writing the new passwords on sticky notes stuck to their monitors.&lt;/p&gt;

&lt;p&gt;That is the entire password industry in one story: we keep making the rules harder without making the passwords better.&lt;/p&gt;

&lt;p&gt;So I did what I always do when I'm annoyed: I built the thing I wish existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool: 100% Client-Side, Nothing Leaves Your Browser
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pragmaticsysadmin.help/tools/password-checker.html" rel="noopener noreferrer"&gt;Try the Password Strength Checker&lt;/a&gt; — paste a password, get an honest strength rating: length, character classes, and common-pattern weaknesses. No account, no tracking, no server. Open the network tab in dev tools if you don't believe me; nothing is sent anywhere.&lt;/p&gt;

&lt;p&gt;Why does that matter? Because a non-trivial number of online "password checkers" submit what you type to their backend for "analysis." Think about that for a second. You are sending your &lt;em&gt;actual password&lt;/em&gt; to a stranger's server to find out if it's good. I built mine specifically so that can't happen — there is no backend. The HTML file doesn't even make a fetch request.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Learned Testing 200 Passwords
&lt;/h2&gt;

&lt;p&gt;I ran a pile of real-world-style passwords through the checker while building it — the kind of passwords people actually use, not the kind security training pretends they use. Three findings surprised me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Length beats cleverness, every time.&lt;/strong&gt; A 20-character passphrase of plain words (&lt;code&gt;correct horse battery staple&lt;/code&gt; style) outscores an 8-character symbol-soup password on every honest metric. The math isn't close: each extra character multiplies the search space, while swapping &lt;code&gt;e&lt;/code&gt; for &lt;code&gt;3&lt;/code&gt; adds almost nothing because attackers already try that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The complexity rules actively hurt.&lt;/strong&gt; Forcing symbols and numbers pushes people toward predictable patterns: &lt;code&gt;Password1!&lt;/code&gt;, &lt;code&gt;Summer2025!&lt;/code&gt;, company-name-plus-year. Attackers' rule lists contain exactly these mutations. A policy that bans those patterns would be ten times more effective than a policy that demands a symbol — but "must contain !" is easier to audit than "must not be predictable," so here we are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Rotation policies create the problem they claim to solve.&lt;/strong&gt; Every 90-day rotation study I've read says the same thing: users increment a number (&lt;code&gt;...Q3&lt;/code&gt; → &lt;code&gt;...Q4&lt;/code&gt;) or rotate through three passwords. The new password is never stronger; it's just newer. If a password is strong and unique, leave it alone. Rotate on &lt;em&gt;suspicion of compromise&lt;/em&gt;, not on a calendar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What To Do Instead (The Pragmatic Version)
&lt;/h2&gt;

&lt;p&gt;For yourself and your users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use a password manager.&lt;/strong&gt; Bitwarden is free and open source. This is the single highest-leverage security change a normal person can make, and I wrote a &lt;a href="https://pragmaticsysadmin.help/senior-tech/2026-07-27-password-manager-for-elderly-parents/" rel="noopener noreferrer"&gt;10-minute setup guide for elderly parents&lt;/a&gt; that works for anyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One long, unique password per service&lt;/strong&gt; — generated, never reused. Reuse is what actually gets accounts taken over, not weak passwords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn on 2FA everywhere that matters&lt;/strong&gt;, especially email — email is the master key to every "forgot password" flow you have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test your own passwords&lt;/strong&gt; with the checker. If your scheme produces anything the tool flags as a common pattern, change the scheme, not the password.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Build Notes (For Fellow Tinkerers)
&lt;/h2&gt;

&lt;p&gt;The whole tool is one self-contained HTML file: vanilla JS, no dependencies, no build step. The scoring is deliberately simple and explainable — entropy estimate from length and character classes, minus penalties for dictionary words, keyboard walks (&lt;code&gt;qwerty&lt;/code&gt;, &lt;code&gt;1qaz&lt;/code&gt;), repeated characters, and date/sequence patterns. No machine learning, no API calls, no analytics.&lt;/p&gt;

&lt;p&gt;Simplicity is the point. A security tool you can't audit is a security tool you shouldn't trust, and "trust me, it runs locally" is a claim anyone can verify in the network tab.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Try it: &lt;a href="https://pragmaticsysadmin.help/tools/password-checker.html" rel="noopener noreferrer"&gt;Password Strength Checker&lt;/a&gt; — free, private, no account. If it saves one person from sending their password to a stranger's server, it was worth the afternoon.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sysadmin</category>
    </item>
    <item>
      <title>A Sandbox for the Linux Commands You're Afraid to Run</title>
      <dc:creator>Jonne Riepponen</dc:creator>
      <pubDate>Mon, 05 Oct 2026 11:55:46 +0000</pubDate>
      <link>https://dev.to/jronegit/a-sandbox-for-the-linux-commands-youre-afraid-to-run-4n3h</link>
      <guid>https://dev.to/jronegit/a-sandbox-for-the-linux-commands-youre-afraid-to-run-4n3h</guid>
      <description>&lt;p&gt;Almost nobody learns &lt;code&gt;rm -rf&lt;/code&gt; the easy way. You read the man page, you use it carefully for a year, and then one day you watch your finger hit Enter a fraction of a second before your brain finishes checking the path.&lt;/p&gt;

&lt;p&gt;The commands that destroy data don't punish typos. They punish the half second before the typo, when you were certain the path was right.&lt;/p&gt;

&lt;p&gt;That half second of doubt isn't something documentation can give you. So I built a place where you can get it wrong on purpose, with nothing at stake but a score.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fake shell that keeps score
&lt;/h2&gt;

&lt;p&gt;The whole thing is one HTML file. No build step, no npm, no CDN — 2,340 lines, about 84 KB, and it runs from a local file if you want it to. You can &lt;a href="https://pragmaticsysadmin.help/tools/command-simulator.html" rel="noopener noreferrer"&gt;try it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The prompt reads &lt;code&gt;sysadmin@danger-zone:~$&lt;/code&gt;, and the &lt;code&gt;~&lt;/code&gt; isn't decoration. Under the surface there's a small state object holding the entire world:&lt;br&gt;
&lt;/p&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;currentDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/home/sysadmin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dangerLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOW&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;commandsUsed&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="na"&gt;safetyScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;currentFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seventeen commands are wired up. &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;cd&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;, &lt;code&gt;df&lt;/code&gt; and &lt;code&gt;ps&lt;/code&gt; handle the boring work, so you can navigate a fake filesystem that behaves the way you expect. The other five are the reason the tool exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every dangerous command costs you twenty points
&lt;/h2&gt;

&lt;p&gt;There is no confirmation prompt anywhere in it. That's on purpose. A real shell doesn't stop to ask, so a simulator that stops to ask teaches you the wrong reflex.&lt;/p&gt;

&lt;p&gt;You pay in score instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dangerous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;triggerDangerEffects&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;safetyScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;safetyScore&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;updateStats&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;Twenty points per mistake, starting from a clean 100. The danger meter recalculates on every keystroke: 80 and above is LOW, 60 is MEDIUM, 40 is HIGH, and anything under that is CRITICAL.&lt;/p&gt;

&lt;p&gt;Five mistakes puts you at zero. That number isn't a punishment. It's a count of how many times you would have needed a restore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The matcher reads tokens, not a real shell
&lt;/h2&gt;

&lt;p&gt;Each dangerous handler inspects the argument array for the exact tokens that make the command lethal:&lt;br&gt;
&lt;/p&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;rmCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&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;args&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="s1"&gt;-rf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&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="s1"&gt;/&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="nx"&gt;args&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="s1"&gt;/home&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="nx"&gt;args&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="s1"&gt;/etc&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="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Files would be deleted in a real system.&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="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;In simulation: No actual files were harmed.&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="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;dangerous&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="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;File deletion simulated safely.&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="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;dangerous&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dd if=/dev/zero of=/dev/sda&lt;/code&gt; triggers an explosion animation. &lt;code&gt;chmod 777 /etc/passwd&lt;/code&gt; and &lt;code&gt;iptables -F&lt;/code&gt; raise their own security warnings. &lt;code&gt;mkfs /dev/sda1&lt;/code&gt; formats nothing and tells you so.&lt;/p&gt;

&lt;p&gt;It's a token check, not a parser. That distinction turns out to matter more than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;rm -rf /*&lt;/code&gt; walks straight through
&lt;/h2&gt;

&lt;p&gt;Here's the hole, and I'd rather you hear it from me than find it yourself.&lt;/p&gt;

&lt;p&gt;The check asks whether &lt;code&gt;args&lt;/code&gt; contains the literal string &lt;code&gt;/&lt;/code&gt;. The command that has actually ended careers is &lt;code&gt;rm -rf /*&lt;/code&gt;, and after splitting on spaces its arguments are &lt;code&gt;['-rf', '/*']&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;'/*'&lt;/code&gt; is not &lt;code&gt;'/'&lt;/code&gt;. No match, no warning, no points deducted. The tool congratulates you: &lt;em&gt;File deletion simulated safely.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Same story with &lt;code&gt;rm -rf ~&lt;/code&gt;, &lt;code&gt;rm -rf $HOME&lt;/code&gt; and &lt;code&gt;rm -rf .&lt;/code&gt;. I matched the memorable version of the footgun and missed the common one. If you want to fix that, the honest version is to test each token with a prefix check, not an equality check:&lt;br&gt;
&lt;/p&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;isRootish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[/&lt;/span&gt;&lt;span class="sr"&gt;~.&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&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;a&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 four lines and it catches the cases I shipped without. I'd rather show you the gap than let the tool quietly lie to you about your own reflexes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two handlers I wrote and could never reach
&lt;/h2&gt;

&lt;p&gt;The dispatcher splits the line on spaces, then switches on the first token:&lt;br&gt;
&lt;/p&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;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&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;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&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="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;catCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&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="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rmCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two cases, though, were written as whole commands with spaces still in them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cat /dev/random&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                 &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;echo 1 &amp;gt; /proc/sys/kernel/panic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;panicCommand&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;break&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;cmd&lt;/code&gt; is &lt;code&gt;parts[0]&lt;/code&gt;. It can never contain a space, so neither case can ever fire. &lt;code&gt;cat /dev/random&lt;/code&gt; falls through to the ordinary &lt;code&gt;cat&lt;/code&gt; branch and gets quietly ignored.&lt;/p&gt;

&lt;p&gt;I found that while going back through the file to write this post. It's the honest argument for writing build logs: the bug had been sitting there since the day I wrote it, and explaining the code out loud is what finally made me look.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually protects your data
&lt;/h2&gt;

&lt;p&gt;A sandbox builds the reflex. It does not protect anything. Before you run anything recursive, work through this list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dry-run it.&lt;/strong&gt; &lt;code&gt;rsync -av --dry-run&lt;/code&gt; and &lt;code&gt;find . -name '*.log' -print&lt;/code&gt; list what would be touched. Read the list properly instead of skimming it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Print the variable.&lt;/strong&gt; &lt;code&gt;echo "$TARGET"&lt;/code&gt; immediately before you delete &lt;code&gt;"$TARGET"&lt;/code&gt;. Most &lt;code&gt;rm -rf&lt;/code&gt; disasters are a variable that expanded to nothing, or to the wrong path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refuse a blank expansion.&lt;/strong&gt; &lt;code&gt;set -u&lt;/code&gt; turns an unset variable into an error instead of a silent empty string, which is the difference between a complaint and a wiped directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check that the mount is mounted.&lt;/strong&gt; A &lt;code&gt;rm -rf&lt;/code&gt; on an unmounted &lt;code&gt;/mnt/backup&lt;/code&gt; cheerfully deletes the real directory sitting underneath it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prove the backup, don't assume it.&lt;/strong&gt; Restore one file from last night before you ever need to restore all of them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://pragmaticsysadmin.help/sysadmin/2025-12-12-the-friday-backup-audit-because-hope-is-not-a-strategy/" rel="noopener noreferrer"&gt;Friday backup audit&lt;/a&gt; is a 20-minute version of that last line, and it beats rebuilding a server on a Saturday. When something does go sideways, &lt;a href="https://pragmaticsysadmin.help/sysadmin/2025-12-17-the-art-of-reading-logs-like-a-detective-finding-needles-in-haystacks/" rel="noopener noreferrer"&gt;reading the logs properly&lt;/a&gt; is where the fix comes from — not from panic.&lt;/p&gt;

&lt;p&gt;The transferable skill is the pause. Practising that pause in a simulator that scores you is cheap. Practising it on your only copy is how the stories start.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's the command you'd never run without &lt;code&gt;--dry-run&lt;/code&gt;? Tell me which one makes you double-check the path.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Related reads:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://pragmaticsysadmin.help/sysadmin/2025-12-12-the-friday-backup-audit-because-hope-is-not-a-strategy/" rel="noopener noreferrer"&gt;The Friday Backup Audit: Because Hope Is Not a Strategy&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://pragmaticsysadmin.help/sysadmin/2025-12-17-the-art-of-reading-logs-like-a-detective-finding-needles-in-haystacks/" rel="noopener noreferrer"&gt;The Art of Reading Logs Like a Detective: Finding Needles in Haystacks&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://pragmaticsysadmin.help/sysadmin/2025-12-09-the-5-minute-server-health-check-that-could-save-your-career/" rel="noopener noreferrer"&gt;The 5-Minute Server Health Check That Could Save Your Career&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>sysadmin</category>
    </item>
  </channel>
</rss>
