<?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: yuan ming</title>
    <description>The latest articles on DEV Community by yuan ming (@yuan_ming_3549dae7e400994).</description>
    <link>https://dev.to/yuan_ming_3549dae7e400994</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%2F4118984%2F9dedadb4-eac0-4dda-9632-33e79dbd28f0.jpg</url>
      <title>DEV Community: yuan ming</title>
      <link>https://dev.to/yuan_ming_3549dae7e400994</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuan_ming_3549dae7e400994"/>
    <language>en</language>
    <item>
      <title>Your Login Tests Are Green. What Did cursor.execute Actually Receive?</title>
      <dc:creator>yuan ming</dc:creator>
      <pubDate>Sat, 12 Sep 2026 12:42:32 +0000</pubDate>
      <link>https://dev.to/yuan_ming_3549dae7e400994/your-login-tests-are-green-what-did-cursorexecute-actually-receive-5a92</link>
      <guid>https://dev.to/yuan_ming_3549dae7e400994/your-login-tests-are-green-what-did-cursorexecute-actually-receive-5a92</guid>
      <description>&lt;p&gt;A reader on my earlier Dev.to post made the useful point directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Assert that the query is parameterized before it reaches &lt;code&gt;cursor.execute&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds simple. It also exposes a blind spot in many test suites. A response assertion can prove that login behaves correctly. It cannot prove what SQL and parameters reached the database executor.&lt;/p&gt;

&lt;p&gt;So I built a small experiment with two login implementations. Both passed the same two behavior tests. Only one passed the parameterization contract test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two implementations
&lt;/h2&gt;

&lt;p&gt;The first implementation builds SQL with an f-string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT id FROM users &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHERE username = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; AND password = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second keeps user values outside the SQL string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT id FROM users WHERE username = ? AND password = ?&lt;/span&gt;&lt;span class="sh"&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;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is not visible in a normal successful response. Both implementations can return &lt;code&gt;200&lt;/code&gt; for valid credentials and &lt;code&gt;401&lt;/code&gt; for invalid credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  The behavior tests are still useful
&lt;/h2&gt;

&lt;p&gt;I ran the same two behavior cases against each implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unsafe_login: valid credentials -&amp;gt; 200, {"ok": True}    PASS
unsafe_login: invalid credentials -&amp;gt; 401, {"ok": False} PASS
safe_login:   valid credentials -&amp;gt; 200, {"ok": True}    PASS
safe_login:   invalid credentials -&amp;gt; 401, {"ok": False} PASS

functional_total=4/4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Across the two implementations, all four behavior test executions passed.&lt;/p&gt;

&lt;p&gt;Those tests should stay. They protect the response contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request parameters -&amp;gt; response body and status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that SQL safety belongs to another boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request parameters -&amp;gt; SQL string and parameters -&amp;gt; cursor.execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A behavior test can stay green while the second path is unsafe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture what cursor.execute received
&lt;/h2&gt;

&lt;p&gt;The experiment uses a small fake database object. It records the SQL and parameters instead of executing real SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FakeDb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;return &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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract test then checks the execution boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_parameterized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the controlled values in this experiment, the unsafe implementation fails because both values are embedded in the SQL string and &lt;code&gt;params&lt;/code&gt; is &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The parameterized implementation passes because the SQL contains placeholders and the values remain in the parameter tuple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PARAMETERIZATION CONTRACT TEST
unsafe_login: FAIL
params=None
sql="SELECT id FROM users WHERE username = 'alice' AND password = 'correct-password'"

safe_login: PASS
params=('alice', 'correct-password')
sql='SELECT id FROM users WHERE username = ? AND password = ?'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact syntax depends on the database driver. SQLite and many supported drivers use &lt;code&gt;?&lt;/code&gt;; psycopg commonly uses &lt;code&gt;%s&lt;/code&gt;; asyncpg commonly uses &lt;code&gt;$1&lt;/code&gt;. Keep the same principle: assert the SQL shape and the parameter payload before execution.&lt;/p&gt;

&lt;p&gt;For a production test, make the assertion as strict as the implementation allows. An approved query constant plus the expected parameter tuple is usually stronger than checking for one character in the SQL string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a scanner to find the review candidate
&lt;/h2&gt;

&lt;p&gt;The contract test proves one boundary that I already know how to exercise. A scanner helps find dangerous patterns earlier, before someone writes that test.&lt;/p&gt;

&lt;p&gt;I ran &lt;code&gt;code-audit-cli&lt;/code&gt; against both files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CODE-AUDIT-CLI SCAN
unsafe_login: findings=1
  severity=high pattern=sql-concat line=6

safe_login: findings=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The finding points to the f-string SQL construction for human review. It does not prove that every possible exploit succeeds, and it does not replace checking the input source, execution path, authorization behavior, or surrounding login logic.&lt;/p&gt;

&lt;p&gt;The three tools have different jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Behavior tests protect the response.
Contract tests protect the execution boundary.
Scanners locate candidate code paths for human review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of them makes the other two redundant.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this experiment does not prove
&lt;/h2&gt;

&lt;p&gt;This is a controlled demonstration, not a complete login-security review.&lt;/p&gt;

&lt;p&gt;It does not cover password hashing, account enumeration, rate limiting, account lockout, audit logging, session handling, or authorization.&lt;/p&gt;

&lt;p&gt;It also does not mean that green tests are useless. It means the test suite should state which contract it is testing. A response contract and an SQL execution contract are not the same contract.&lt;/p&gt;

&lt;p&gt;The practical check is short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When login runs, what exact SQL and parameter tuple reaches cursor.execute?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the test cannot answer that, the response assertion may be hiding the most important part of the path.&lt;/p&gt;

&lt;p&gt;The public rules and sample output are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yuan1521913/code-audit-cli" rel="noopener noreferrer"&gt;https://github.com/yuan1521913/code-audit-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scanner runs locally and does not upload the project. If you want to run the same local scanner and inspect its complete source and rules, the licensed source package is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://5552463341538.gumroad.com/l/code-audit-cli-source" rel="noopener noreferrer"&gt;code-audit-cli source package&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What does your test suite capture at &lt;code&gt;cursor.execute&lt;/code&gt;?&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>sql</category>
      <category>testing</category>
    </item>
    <item>
      <title>I Stopped a Raft Leader in a 3-Node Go KV Store. Here Is the Evidence.</title>
      <dc:creator>yuan ming</dc:creator>
      <pubDate>Sat, 12 Sep 2026 12:35:26 +0000</pubDate>
      <link>https://dev.to/yuan_ming_3549dae7e400994/i-stopped-a-raft-leader-in-a-3-node-go-kv-store-here-is-the-evidence-4ac2</link>
      <guid>https://dev.to/yuan_ming_3549dae7e400994/i-stopped-a-raft-leader-in-a-3-node-go-kv-store-here-is-the-evidence-4ac2</guid>
      <description>&lt;p&gt;Most Raft projects look healthy when every node is still running.&lt;/p&gt;

&lt;p&gt;The useful question is narrower: after a leader stops, what exactly can the&lt;br&gt;
remaining nodes prove?&lt;/p&gt;

&lt;p&gt;I built RaftKV, a teaching-grade three-node Raft sharded key-value store in Go.&lt;br&gt;
For this test I did not want another architecture diagram. I wanted a recorded&lt;br&gt;
flow with a committed key, a stopped node, a failover, a read, and a new write.&lt;/p&gt;

&lt;p&gt;This post walks through that flow and separates the evidence from the claims the&lt;br&gt;
test does not support.&lt;/p&gt;
&lt;h2&gt;
  
  
  The cluster
&lt;/h2&gt;

&lt;p&gt;RaftKV starts three independent node processes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;node-a&lt;/code&gt;, &lt;code&gt;node-b&lt;/code&gt;, and &lt;code&gt;node-c&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;two shards: &lt;code&gt;shard-0&lt;/code&gt; and &lt;code&gt;shard-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;one Raft group per shard&lt;/li&gt;
&lt;li&gt;three replicas in each Raft group&lt;/li&gt;
&lt;li&gt;real HTTP RPCs for &lt;code&gt;RequestVote&lt;/code&gt; and &lt;code&gt;AppendEntries&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;client requests accepted by any node, then forwarded to the shard leader&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is that the nodes are separate processes. A leader election&lt;br&gt;
is not a method call inside one test binary.&lt;/p&gt;

&lt;p&gt;Before the fault, the health endpoint returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RaftKV Trial 2026-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"leaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maintenance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shards"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"raft-kv-1.0.2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two shards, two leaders, three live nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: commit a key
&lt;/h2&gt;

&lt;p&gt;I wrote a key through the public HTTP API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trial:user:1001 = RaftKV-ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key was routed to &lt;code&gt;shard-1&lt;/code&gt;. The write had to go through the shard leader,&lt;br&gt;
reach a Raft majority, commit, and then apply to the state machine.&lt;/p&gt;

&lt;p&gt;This distinction matters. A value stored in memory on one node is not the same&lt;br&gt;
as a committed Raft entry replicated to a majority.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: stop node-a
&lt;/h2&gt;

&lt;p&gt;While the cluster was healthy, I stopped &lt;code&gt;node-a&lt;/code&gt;, which had &lt;code&gt;node_id 0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I did not stop a request handler or change a flag in a test. The process&lt;br&gt;
disappeared.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: ask the surviving nodes
&lt;/h2&gt;

&lt;p&gt;After waiting for a new election, both surviving nodes reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RaftKV Trial 2026-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"leaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maintenance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"node_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shards"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"uptime_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"raft-kv-1.0.2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The affected shard had a new leader. The old leader was no longer part of the&lt;br&gt;
live majority.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: read the committed key
&lt;/h2&gt;

&lt;p&gt;The previously committed key was still available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"found"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trial:user:1001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shard-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RaftKV-ok"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part that connects Raft theory to an operational result. The key&lt;br&gt;
survived because it had already been committed by a majority, not because one&lt;br&gt;
process happened to keep a copy in memory.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: write again
&lt;/h2&gt;

&lt;p&gt;A cluster that can only read after failover is not enough. I wrote a new key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;failover:after:node0 = still-writable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new value was read back successfully.&lt;/p&gt;

&lt;p&gt;The recorded evidence summary is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RaftKV fault evidence: stopped node-a (node_id 0), then queried node-b and node-c.
health post: {"cluster":"RaftKV Trial 2026-09-06","leaders":2,"maintenance":false,"node_id":2,"shards":2,"status":"ok","uptime_s":81,"version":"raft-kv-1.0.2"}
read post: RaftKV-ok
write post: still-writable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this test proves
&lt;/h2&gt;

&lt;p&gt;In this implementation and this recorded environment, a three-node Raft group&lt;br&gt;
continued to serve committed data after one node stopped. A new leader was&lt;br&gt;
elected, the old committed value was readable, and the cluster accepted a new&lt;br&gt;
write.&lt;/p&gt;

&lt;p&gt;The same public evidence repository also records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;three-node HTTP Raft read and write tests&lt;/li&gt;
&lt;li&gt;follower restart and catch-up coverage&lt;/li&gt;
&lt;li&gt;restart recovery&lt;/li&gt;
&lt;li&gt;dynamic shard add and migration recovery&lt;/li&gt;
&lt;li&gt;snapshot restore&lt;/li&gt;
&lt;li&gt;concurrent write visibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Raw files include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;evidence/trial-health-pre.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evidence/trial-status-pre.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evidence/trial-read-post.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evidence/trial-write-post.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evidence/fault-post-summary.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evidence/go-test-output.txt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it does not prove
&lt;/h2&gt;

&lt;p&gt;I do not treat this experiment as proof that RaftKV is production-ready.&lt;/p&gt;

&lt;p&gt;It does not prove:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;correct behavior under every network partition&lt;/li&gt;
&lt;li&gt;disk-failure or fsync-failure correctness&lt;/li&gt;
&lt;li&gt;cross-data-center replication&lt;/li&gt;
&lt;li&gt;production-grade consistency guarantees&lt;/li&gt;
&lt;li&gt;performance under production traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RaftKV is teaching-grade and portfolio-grade software. It is not a replacement&lt;br&gt;
for Redis, etcd, or TiKV.&lt;/p&gt;

&lt;p&gt;That boundary is not a disclaimer added at the end. It changes how the project&lt;br&gt;
should be reviewed. The goal is to make the implementation and failure behavior&lt;br&gt;
inspectable, not to pretend a small system has covered every production failure&lt;br&gt;
mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a better project story
&lt;/h2&gt;

&lt;p&gt;A project description that says "implemented Raft, sharding, and failover" is&lt;br&gt;
difficult to verify.&lt;/p&gt;

&lt;p&gt;A stronger story separates four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The design choice.&lt;/li&gt;
&lt;li&gt;The failure that was injected.&lt;/li&gt;
&lt;li&gt;The observed result.&lt;/li&gt;
&lt;li&gt;The remaining uncertainty.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a backend interview, that structure also creates better follow-up&lt;br&gt;
questions. Why did the committed key survive? What happens to an uncommitted&lt;br&gt;
entry? What happens when the old leader restarts? What still breaks under a&lt;br&gt;
network partition?&lt;/p&gt;

&lt;p&gt;Those questions are more useful than another list of features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The public evidence repository is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yuan1521913/raft-kv" rel="noopener noreferrer"&gt;https://github.com/yuan1521913/raft-kv&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full Go source, tests, Docker Compose setup, management dashboard, and&lt;br&gt;
teaching materials are part of the licensed source package.&lt;/p&gt;

&lt;p&gt;If you want to run the Windows trial first:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://5552463341538.gumroad.com/l/raftkv-trial?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=raftkv_launch" rel="noopener noreferrer"&gt;https://5552463341538.gumroad.com/l/raftkv-trial?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=raftkv_launch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you build distributed systems, I would be interested in the failure test you&lt;br&gt;
consider mandatory before trusting a Raft implementation.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>An AI-generated login endpoint "works" - I still found SQL concatenation before launch</title>
      <dc:creator>yuan ming</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:47:45 +0000</pubDate>
      <link>https://dev.to/yuan_ming_3549dae7e400994/an-ai-generated-login-endpoint-works-i-still-found-sql-concatenation-before-launch-2om8</link>
      <guid>https://dev.to/yuan_ming_3549dae7e400994/an-ai-generated-login-endpoint-works-i-still-found-sql-concatenation-before-launch-2om8</guid>
      <description>&lt;p&gt;Functional tests passing is not the same as code being safe to ship. Here is a reproducible case: an AI-generated login endpoint that behaves correctly, the scan finding I checked before launch, and the fix that made the pattern disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code runs, but I would not ship it like this
&lt;/h2&gt;

&lt;p&gt;This is a local demo equivalent of a login endpoint, not live production code and not a client project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT id FROM users WHERE username = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; AND password = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works: correct credentials return success. My concern is not whether it works today. It is that &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; come from an HTTP request and are placed directly inside an SQL string.&lt;/p&gt;

&lt;h2&gt;
  
  
  A first-pass scan finds the pattern to check
&lt;/h2&gt;

&lt;p&gt;I did not read every line first. I ran a local quick scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code-audit app &lt;span class="nt"&gt;--format&lt;/span&gt; html &lt;span class="nt"&gt;--output&lt;/span&gt; report.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One result was:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sql-concat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SQL assembled from strings, user input can reach the query&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app/login.py:12&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A high finding is not an automatic conclusion. I confirm it in four steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where does the input come from?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; come from &lt;code&gt;request.form&lt;/code&gt;. That means a client can submit arbitrary values. Input from HTTP requests is untrusted by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Where does it go?
&lt;/h2&gt;

&lt;p&gt;The values skip length checks, type checks, escaping and parameterization, then enter the SQL template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT id FROM users WHERE username = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; AND password = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source is a request parameter. The sink is SQL execution. There is no boundary between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Can it be exploited?
&lt;/h2&gt;

&lt;p&gt;I do not attack my own project. I reason through SQL syntax:&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;username&lt;/code&gt; contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;' OR '1'='1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the resulting SQL can become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{password}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I cannot prove that every login is bypassable. I can confirm there is a suspicious path that does not require advanced exploitation. For a login endpoint, fixing it before launch is cheaper than investigating it after launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Use a parameterized query
&lt;/h2&gt;

&lt;p&gt;The fix is not filtering single quotes. It is keeping user input out of the SQL string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT id, password_hash FROM users WHERE username = %s&lt;/span&gt;&lt;span class="sh"&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;username&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;verify_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password_hash&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also changes the login flow to fetch the password hash by username first and verify the password separately, instead of putting the plaintext password into SQL.&lt;/p&gt;

&lt;p&gt;After the fix, the &lt;code&gt;sql-concat&lt;/code&gt; High finding no longer appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  A clean scan is not "absolutely safe"
&lt;/h2&gt;

&lt;p&gt;A clean scan only means this round did not find that rule pattern. It does not mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are no other vulnerabilities.&lt;/li&gt;
&lt;li&gt;The login flow meets every production security requirement.&lt;/li&gt;
&lt;li&gt;You can skip rate limiting, lockout policy, audit logging and access checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pre-launch checking is a process, not a verdict: scan for suspicious locations, confirm with a human, fix what can be fixed, and list what still needs review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence you can inspect
&lt;/h2&gt;

&lt;p&gt;The rules, sample output and boundaries are public on GitHub: &lt;a href="https://github.com/yuan1521913/code-audit-cli" rel="noopener noreferrer"&gt;https://github.com/yuan1521913/code-audit-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full source package is a separate licensed delivery. If you want a local scanner that produces Markdown, JSON, HTML or SARIF reports and can act as a CI gate, the English source package is here: &lt;a href="https://5552463341538.gumroad.com/l/code-audit-cli-source" rel="noopener noreferrer"&gt;https://5552463341538.gumroad.com/l/code-audit-cli-source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scanner runs locally and does not upload your code. Automatic results still need human confirmation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>security</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
