<?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: vladimirrotariu</title>
    <description>The latest articles on DEV Community by vladimirrotariu (@vladimirrotariu).</description>
    <link>https://dev.to/vladimirrotariu</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%2F1108799%2F4aba53d9-5185-40bc-abc0-1520bb5b362c.jpeg</url>
      <title>DEV Community: vladimirrotariu</title>
      <link>https://dev.to/vladimirrotariu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vladimirrotariu"/>
    <language>en</language>
    <item>
      <title>10 of 11 AI coding agents fell to the same shell trick</title>
      <dc:creator>vladimirrotariu</dc:creator>
      <pubDate>Wed, 19 Aug 2026 14:58:57 +0000</pubDate>
      <link>https://dev.to/vladimirrotariu/10-of-11-ai-coding-agents-fell-to-the-same-shell-trick-4ko5</link>
      <guid>https://dev.to/vladimirrotariu/10-of-11-ai-coding-agents-fell-to-the-same-shell-trick-4ko5</guid>
      <description>&lt;p&gt;Adversa AI disclosed GuardFall on 30 June 2026. Their bypasses got past the command filter in 10 of 11 open-source coding and computer-use agents. One held, Continue, and it held because it parses the command before judging it, rather than matching patterns in raw text.&lt;/p&gt;

&lt;p&gt;The trick is decades old. The guard inspects the string the model produced, and bash rewrites that string before running it. A regex reads &lt;code&gt;r''m&lt;/code&gt; and finds nothing to match, then bash strips the quotes and runs &lt;code&gt;rm&lt;/code&gt;. &lt;code&gt;$IFS&lt;/code&gt; expansion and command substitution do the same. Two parsers read the same bytes and disagree, and the second one runs as root.&lt;/p&gt;

&lt;p&gt;Tightening the allowlist leaves that open, because the gap is the string itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed actions instead
&lt;/h2&gt;

&lt;p&gt;I build &lt;a href="https://github.com/lacs-project/sysknife" rel="noopener noreferrer"&gt;SysKnife&lt;/a&gt;, a Linux sysadmin co-pilot that runs as an MCP server and accepts typed actions only. The tool surface carries no &lt;code&gt;exec&lt;/code&gt; and no &lt;code&gt;sudo-exec&lt;/code&gt;, so nothing on it takes text from the model and hands it to a shell. &lt;code&gt;AptInstall { package: "ripgrep" }&lt;/code&gt; is a schema-checked value, and I compiled the daemon that carries it out against a fixed catalogue of 190 typed actions it cannot extend at runtime. An action outside that catalogue has no representation to reject.&lt;/p&gt;

&lt;p&gt;Three properties follow from that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent cannot approve its own work.&lt;/strong&gt; &lt;code&gt;sysknife_plan&lt;/code&gt; returns typed steps with risk levels and a transaction id, then stops. Execution needs a receipt that only &lt;code&gt;sysknife approve &amp;lt;transaction-id&amp;gt;&lt;/code&gt; mints, typed by a human at a real terminal, valid once and for fifteen minutes. The daemon checks that receipt where the privilege sits, so an agent that talks itself into having permission gets refused anyway. There is no phrasing of "yes, go ahead" inside a chat window that substitutes for it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flacs-project%2Fsysknife%2Fmain%2Fassets%2Fdemo%2Fmcp-flow.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flacs-project%2Fsysknife%2Fmain%2Fassets%2Fdemo%2Fmcp-flow.gif" alt="Claude Code calling sysknife_plan, a human approving in a second terminal, and sysknife_execute consuming the one-time receipts" width="600" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A deterministic reproduction of the Claude Code MCP flow, rendered offline by &lt;a href="https://github.com/lacs-project/sysknife/blob/main/assets/demo/mcp-flow-mock.sh" rel="noopener noreferrer"&gt;mcp-flow-mock.sh&lt;/a&gt; so it replays identically from a fresh checkout. Live LLM calls are nondeterministic and the tape renders with no daemon or provider configured, so the recording is scripted rather than captured. The same flow works in Cursor and Codex CLI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privilege stays behind a boundary the model cannot reach.&lt;/strong&gt; The MCP server runs unprivileged. Every mutation travels to &lt;code&gt;sysknife-daemon&lt;/code&gt; over a unix socket at mode &lt;code&gt;0660&lt;/code&gt;, inside a directory at &lt;code&gt;0750 sysknife:sysknife&lt;/code&gt;, and the daemon holds the sudoers and polkit policy. It reads the caller's identity from &lt;code&gt;SO_PEERCRED&lt;/code&gt;, so the kernel names who connected and the request body never gets to claim it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The daemon signs what it executes, and you can check the signature without the daemon.&lt;/strong&gt; Rows join an Ed25519 hash chain that verifies against the exported public key alone, with no signing key and no access to the host that wrote the log. A row names the account that asked for it: &lt;code&gt;uid:1000&lt;/code&gt;, or &lt;code&gt;token:vsock&lt;/code&gt; when the caller presented a shared secret, or &lt;code&gt;none:unattributed&lt;/code&gt; when the daemon could not establish an account. Signing an honest "I could not tell" beats signing a uid the kernel never vouched for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx sysknife-setup &lt;span class="nt"&gt;--codex&lt;/span&gt;
sysknife audit verify &lt;span class="nt"&gt;--pubkey&lt;/span&gt; audit-key.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first appends an MCP block to &lt;code&gt;~/.codex/config.toml&lt;/code&gt; and writes an &lt;code&gt;AGENTS.md&lt;/code&gt;, then downloads binaries it checks against a published SHA-256 sums file. The second is the one an auditor runs, and it needs nothing but the public key.&lt;/p&gt;

&lt;p&gt;Codex is one of three targets. &lt;code&gt;--claude&lt;/code&gt; merges &lt;code&gt;.mcp.json&lt;/code&gt; and writes a rules file under &lt;code&gt;.claude/&lt;/code&gt;, which is the setup the recording above shows. &lt;code&gt;--cursor&lt;/code&gt; writes &lt;code&gt;.cursor/mcp.json&lt;/code&gt; and its own rules file. &lt;code&gt;--all&lt;/code&gt; does the three together, and the installer with no flag asks you which you want. The daemon and the approval boundary are identical underneath: the agent surface changes, the privilege boundary does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support, stated by evidence
&lt;/h2&gt;

&lt;p&gt;Ubuntu 20.04 and later are eligible, LTS and interim alike. Three of them, 22.04, 24.04 and 26.04, each passed the full story suite 79 of 79 on a live VM, each with a committed replay twin that reproduces the run offline. The support matrix lists the evidence release by release, so a release the suite has never run on says so. Fedora Atomic 41 and later is implemented and eligible, and waiting on a current VM run. MIT licence, and the code sends no telemetry.&lt;/p&gt;

&lt;p&gt;One caveat about directory listings, including the ones that carry a green badge for this project. Boot the server inside a build container and all five MCP tools show up, because a tool list is static metadata and no privileged daemon exists in a sandbox. A green run there proves the binary starts and the schemas parse, and stops short of proving it administers a real host. For that part you have the VM runs, release by release, and a signed chain you can check for yourself.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>linux</category>
      <category>rust</category>
    </item>
    <item>
      <title>Your terminal can't render LaTeX. Here's a 10-second fix for Claude Code and Codex</title>
      <dc:creator>vladimirrotariu</dc:creator>
      <pubDate>Sat, 18 Jul 2026 01:36:22 +0000</pubDate>
      <link>https://dev.to/vladimirrotariu/your-terminal-cant-render-latex-heres-a-10-second-fix-for-claude-code-and-codex-1adf</link>
      <guid>https://dev.to/vladimirrotariu/your-terminal-cant-render-latex-heres-a-10-second-fix-for-claude-code-and-codex-1adf</guid>
      <description>&lt;p&gt;Ask Claude Code or Codex to explain some math and your terminal shows this:&lt;/p&gt;

&lt;p&gt;H(p) = $-\sum_{i} p_{i} \log p_{i}$&lt;/p&gt;

&lt;p&gt;That is LaTeX. A browser renders it into a clean equation. A terminal renders it into dollar-sign noise, because a TUI has no math engine to run. The more math-heavy your work (ML, statistics, optimization, proofs), the worse the output gets exactly where you need it clear.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feotbayi7mxmwwc0xc9ss.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feotbayi7mxmwwc0xc9ss.gif" alt="claude-math: raw LaTeX versus readable Unicode" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix is not "render the LaTeX"
&lt;/h2&gt;

&lt;p&gt;That is the hard, unsolved path: it needs a graphics backend like Kitty or SIXEL, plus a fallback for SSH and tmux. There is a cheaper move. The model does not have to output LaTeX at all.&lt;/p&gt;

&lt;p&gt;Unicode already has most inline math: ∑, ∫, √, ≤, ≥, ∈, α, β, σ, x², pᵢ, set notation, even small matrices. A terminal renders all of it, because it is just text. So you teach the model to skip LaTeX and emit the Unicode directly:&lt;/p&gt;

&lt;p&gt;before:  H(p) = $-\sum_{i} p_{i} \log p_{i}$&lt;br&gt;
after:   H(p) = − ∑ᵢ pᵢ · log pᵢ&lt;/p&gt;

&lt;h2&gt;
  
  
  claude-math
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/vladimirrott/claude-math" rel="noopener noreferrer"&gt;&lt;code&gt;claude-math&lt;/code&gt;&lt;/a&gt; does exactly that. It has no runtime; it is a &lt;code&gt;SKILL.md&lt;/code&gt; the agent loads, with a glyph cheatsheet so the mapping stays consistent. It is copy- and search-safe (plain Unicode, not the "mathematical alphanumeric" styling codepoints that break find-in-page), and the same file works in Claude Code and Codex.&lt;/p&gt;

&lt;p&gt;npx claude-math install          # Claude Code&lt;br&gt;
npx claude-math install --codex  # Codex CLI&lt;br&gt;&lt;br&gt;
Restart your agent and math comes back as text you can read.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;One caveat, stated plainly:&lt;/strong&gt; this is a workaround, not native rendering. A dense derivation is still clearer as typeset math, and on a GUI surface that already renders LaTeX you want the LaTeX, not Unicode. claude-math is deliberately a terminal tool. Buh that fills an agent's answers, it is an immediate improvement.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/vladimirrott/claude-math" rel="noopener noreferrer"&gt;https://github.com/vladimirrott/claude-math&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the Unicode mapping ever looks wrong, that is exactly the kind of issue I want to hear about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>claude</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
