<?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>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>
