<?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: seamstressdev</title>
    <description>The latest articles on DEV Community by seamstressdev (@seamstress).</description>
    <link>https://dev.to/seamstress</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%2F4018236%2Fe43097e6-e9dd-4d09-8991-6020f91e4fc0.png</url>
      <title>DEV Community: seamstressdev</title>
      <link>https://dev.to/seamstress</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seamstress"/>
    <language>en</language>
    <item>
      <title>I built training wheels for AI coding agents, then found a critical bug in them by pointing my code reviewer at them</title>
      <dc:creator>seamstressdev</dc:creator>
      <pubDate>Wed, 08 Jul 2026 18:25:03 +0000</pubDate>
      <link>https://dev.to/seamstress/i-built-training-wheels-for-ai-coding-agents-then-found-a-critical-bug-in-them-by-pointing-my-code-1c7j</link>
      <guid>https://dev.to/seamstress/i-built-training-wheels-for-ai-coding-agents-then-found-a-critical-bug-in-them-by-pointing-my-code-1c7j</guid>
      <description>&lt;p&gt;I build coding tools with one rule: the tool has to survive its own methodology.&lt;/p&gt;

&lt;p&gt;SeamStress is a code reviewer scoped to seams. The places where money, authorization, and tenant isolation cross. Last week I built a second thing next to it: SeamStress Scaffold, a set of five behavior protocols that install into a coding agent and make it careful exactly where seam bugs live. Read the neighbors before editing money code. Stop before anything irreversible. Quote the code instead of claiming what it does. Never edit a red test to green without saying the promise changed out loud.&lt;/p&gt;

&lt;p&gt;The protocols are training wheels. The premise is that AI assisted development manufactures the exact condition seam bugs need: many fast sessions, each one locally correct, none holding the whole. So the agent gets questions at the boundaries and silence everywhere else.&lt;/p&gt;

&lt;p&gt;Then I did the thing the rule requires. I pointed the code reviewer at the training wheels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The seam I did not see&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The scaffold ships a CLI that reads a repository and writes a map of its risky files. One protocol then tells the agent to read that map first, as trusted orientation, before it does anything.&lt;/p&gt;

&lt;p&gt;Say that slowly. The tool reads an arbitrary repository, including repositories I do not trust, because auditing strangers' code is the whole point. It takes content from that repository and writes it into a document. Then it tells the agent to trust that document.&lt;/p&gt;

&lt;p&gt;That is untrusted input crossing into trusted context. It is a seam. It is exactly the class of bug the tool exists to find, and it was sitting in the tool, and I had not seen it, because it lives between three files that each look correct alone. The scanner reads a path. The renderer writes it into markdown. The protocol tells the agent to trust the result. No single file is wrong. The combination is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the audit found&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran the same thing on it that I run on client code: a blind trio. Three critics, fresh context, no shared state, different models. One of them got no threat model at all, just review this code for correctness and safety, because the honest question is not whether a bug is findable when you are told where to look. It is whether a reviewer meets it cold.&lt;/p&gt;

&lt;p&gt;It was found cold. Critic A, no framing, found the injection, and the other two reproduced it. The enabling fact is small and verifiable: a filename on Linux can contain newlines and every markdown metacharacter, and the scanner imposed no policy on what it read. A crafted filename could write a fake heading into the map. The parser would then promote that attacker text into the section it treats as human curated truth, the section that survives even when you delete the file.&lt;/p&gt;

&lt;p&gt;Nine findings. Three critical. The worst of them let a hostile repository plant text into the document my own protocol told the agent to trust.&lt;/p&gt;

&lt;p&gt;Every finding was verified the way the tool verifies its own output: re run against the real functions, exact input demonstrated, and anything that did not reproduce marked refuted rather than reported. One critic claimed a crash that turned out to terminate cleanly. That claim is labeled refuted in the record, and chasing it is what surfaced a real out of tree file read the flashy version had hidden. The harnesses that demonstrate each finding are committed in the repo. You can run them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I did about it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fixed all nine. Engine first where the shared code was implicated, because the same unsanitized path fed the paid reviewer's report too, and its clients receive those reports.&lt;/p&gt;

&lt;p&gt;Every fix had to flip its harness from exploit demonstrated to refuted, proven by running it, or be pinned by a test that fails against the old code. The paid pipeline's detection had to come out byte for byte identical, proven against a snapshot taken before the fixes, because a security patch that quietly changes what the tool detects is a different decision that I do not get to make silently. It held. The record, findings and severities and fixing commits and the reproduction harnesses and the raw critic outputs, is public in the repo.&lt;/p&gt;

&lt;p&gt;I gated the launch on the fixes. The repository was public the whole time, because the commit history is the point, but I did not point anyone at it until the criticals were closed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this is and is not&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The scaffold is free, MIT, and early. It installs five protocols into a coding agent and asks questions at seams. It does not guarantee safety. Where the irreversible line sits for a genuinely new task is human judgment, and the protocols say so in their own text. They force the thinking. They cannot do the thinking.&lt;/p&gt;

&lt;p&gt;And the honest residual from the audit is in the record too: one of the fixes anchors trusted content with a marker that is a public string, not a secret, so a determined forger with repository access is a problem I have named and not yet fully solved. Writing that down is cheaper than pretending it is closed.&lt;/p&gt;

&lt;p&gt;Here is the whole argument in one line. A tool that claims to make AI assisted code safer should survive being audited by its own methodology, and the honest version of that story includes the part where it did not, until it was fixed. Confidence should be earned the same way the tool earns a finding. Checked against the real code, exact lines quoted, misses on the record next to the hits.&lt;/p&gt;

&lt;p&gt;SeamStress Scaffold: &lt;a href="https://github.com/SeamStressDev/seam-scaffold" rel="noopener noreferrer"&gt;https://github.com/SeamStressDev/seam-scaffold&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reviewer that found the bug: &lt;a href="https://github.com/SeamStressDev/seamstress" rel="noopener noreferrer"&gt;https://github.com/SeamStressDev/seamstress&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full audit record, harnesses included: &lt;a href="https://github.com/SeamStressDev/seam-scaffold/tree/main/validation/audit" rel="noopener noreferrer"&gt;https://github.com/SeamStressDev/seam-scaffold/tree/main/validation/audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;by Nate. I build SeamStress, open source tools for catching the bugs that live where AI assisted code tends to break.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>I pointed my code reviewer at its own verifier. It found two ways to lie.</title>
      <dc:creator>seamstressdev</dc:creator>
      <pubDate>Mon, 06 Jul 2026 18:15:09 +0000</pubDate>
      <link>https://dev.to/seamstress/i-pointed-my-code-reviewer-at-its-own-verifier-it-found-two-ways-to-lie-57bc</link>
      <guid>https://dev.to/seamstress/i-pointed-my-code-reviewer-at-its-own-verifier-it-found-two-ways-to-lie-57bc</guid>
      <description>&lt;p&gt;I built SeamStress. It's a code reviewer with one rule: it only reports what it can prove against your actual code, quoting the exact lines. If it can't prove it, the finding gets demoted to a judgment call. Not presented as fact.&lt;/p&gt;

&lt;p&gt;That rule is enforced by one small piece of code: the verification gate. It decides whether a finding may be shown as verified_real. Every other part of the tool can be wrong and the damage is bounded. If the gate is wrong, the tool shows you a confident claim it never earned, with a proof label on it, and it renders as success. Silently.&lt;/p&gt;

&lt;p&gt;So before making the repo public, I ran the tool on the gate. Same pipeline it runs on anyone's code: three blind critics, then synthesis, then per finding verification. Eight model calls. It found two critical defects in its own foundation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defect one: verified with no evidence behind it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The status authority looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;verifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findingId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;result&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;status&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unverified&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It trusted the verdict on a finding ID match. It never looked at the evidence. And the schema allowed an empty evidence array and an empty quoted code string. So a result shaped like {status: "verified_real", evidence: []} validated cleanly and certified a finding as proven. The report renderer would put that finding in the headline, under copy promising the exact lines quoted as proof, with nothing attached. The evidence block suppressed the display of the missing proof. It did not remove the finding from the verified set.&lt;/p&gt;

&lt;p&gt;The fix lives at the authority, not just the schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;result&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;unverified&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;hasRealEvidence&lt;/span&gt; &lt;span class="o"&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;evidence&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;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quotedCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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;return&lt;/span&gt; &lt;span class="nx"&gt;hasRealEvidence&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;status&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unverified&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A verdict is honored only when at least one non empty quote backs it. Checking at the authority also catches the whitespace quote variant that a naive schema minimum would miss. Fixed in &lt;a href="https://github.com/SeamStressDev/seamstress/commit/5fdd680" rel="noopener noreferrer"&gt;5fdd680&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defect two: proof attached to the wrong finding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finding IDs were namespaced by a slug of the file path. The slug lowercases and collapses every non alphanumeric run to a dash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&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;a-zA-Z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/g&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="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^-+|-+$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two different paths can slug to the same value. a/Check.ts and a-check.ts. user_check and user-check. When that happens, both seams share a namespace prefix, their finding IDs alias, and the first match lookup binds one seam's verified status and quoted evidence to the other seam's finding. Fabricated proof, on a finding that was never verified. It needs a colliding path pair to trigger, which is rare. The outcome when it triggers is the worst one available.&lt;/p&gt;

&lt;p&gt;The fix keys the namespace on the seam's position in the map, which is unique by construction. No collision can produce a duplicate ID. Fixed in &lt;a href="https://github.com/SeamStressDev/seamstress/commit/bb9c838" rel="noopener noreferrer"&gt;bb9c838&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trust the tests, not the story&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both fixes are pinned by regression tests, and the tests are reversion proven. That is not a figure of speech. We reverted each fix in a scratch worktree, with the tests held at HEAD, and watched the guards go red on the pre fix code while the positive control stayed green. Revert the evidence gate and two tests fail. Revert the collision fix and the binding test fails. Restore either fix and the suite is green again. You can do the same thing yourself. The tests are in src/types/types.test.ts and src/engine/detector.test.ts.&lt;/p&gt;

&lt;p&gt;One related gap was found and knowingly left open. An orphaned verification, one whose finding ID matches nothing, silently degrades its finding to unverified. That is under reporting. It fails in the safe direction, toward silence instead of false assurance. So it was documented rather than fixed. Not every finding deserves a commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it misses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tool has recorded failures and they are public. The benchmark scores real bugs reconstructed from documented incidents on an append only ledger, and the ledger includes the misses. One fixture never even reached review: the keyword pre filter scored it at zero and the run cost nothing because no model was called. That is a detection stage miss and it is the concrete instance of a limit we track. It's on the ledger. Not edited out.&lt;/p&gt;

&lt;p&gt;The broader limits are stated in the repo. It's early, bring your own key, validated on a small number of repos. It reasons about the code in front of it, so it can't see design intent that lives in your issue tracker. A finding can be code accurate and still be something you already decided on purpose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why publish this&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the audit is the argument. A code reviewer that claims a proof standard should survive its own methodology, and the honest version of that story includes the part where it didn't, twice, until the gate was fixed. Confidence in the tool should be earned the same way the tool earns its findings. Checked against the real code, exact lines quoted.&lt;/p&gt;

&lt;p&gt;The full engineering record, including the verifier's quoted evidence for each finding, is in the repo: &lt;a href="https://github.com/SeamStressDev/seamstress/blob/main/docs/seamstress-trust-gate-trio.md" rel="noopener noreferrer"&gt;docs/seamstress-trust-gate-trio.md&lt;/a&gt;. The fastest way to see real output is &lt;a href="https://github.com/SeamStressDev/seamstress/tree/main/examples" rel="noopener noreferrer"&gt;examples/&lt;/a&gt;. The benchmark, misses included, is &lt;a href="https://github.com/SeamStressDev/seamstress/tree/main/benchmark" rel="noopener noreferrer"&gt;benchmark/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Implementation is AI assisted. Architecture, validation, and every irreversible call are mine.&lt;/p&gt;

&lt;p&gt;SeamStress is open source: &lt;a href="https://github.com/SeamStressDev/seamstress" rel="noopener noreferrer"&gt;https://github.com/SeamStressDev/seamstress&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>testing</category>
      <category>ai</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
