<?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: DiffSense</title>
    <description>The latest articles on DEV Community by DiffSense (@diffsense).</description>
    <link>https://dev.to/diffsense</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3837904%2Fbd199cda-5749-4552-9cc9-014e6cc4dc7d.jpg</url>
      <title>DEV Community: DiffSense</title>
      <link>https://dev.to/diffsense</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/diffsense"/>
    <language>en</language>
    <item>
      <title>Catch Terraform Security Issues Before They Hit Production — With a Single API Call</title>
      <dc:creator>DiffSense</dc:creator>
      <pubDate>Mon, 23 Mar 2026 20:58:07 +0000</pubDate>
      <link>https://dev.to/diffsense/catch-terraform-security-issues-before-they-hit-production-with-a-single-api-call-4ijd</link>
      <guid>https://dev.to/diffsense/catch-terraform-security-issues-before-they-hit-production-with-a-single-api-call-4ijd</guid>
      <description>&lt;p&gt;tags: terraform, devsecops, security, iac&lt;/p&gt;

&lt;p&gt;You've just pushed a Terraform change. The plan looks clean. The apply succeeds. Three weeks later, someone runs a routine audit and finds your EC2 instance has been exposed to the entire internet since day one — because a security group was accidentally left wide open.&lt;/p&gt;

&lt;p&gt;This is not a hypothetical. It's a pattern that shows up repeatedly in post-mortems, and it almost always comes down to the same root cause: nobody checked the HCL before it shipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TerraGuard&lt;/strong&gt; is a REST API that does exactly that check — static analysis of Terraform code for security misconfigurations and hardcoded secrets, with no tooling to install and no pipeline plugins to configure.&lt;/p&gt;




&lt;h2&gt;
  
  
  What TerraGuard Does
&lt;/h2&gt;

&lt;p&gt;TerraGuard exposes two analysis endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /analyze&lt;/code&gt; — scans HCL for security misconfigurations (open ingress rules, overly permissive IAM policies, unencrypted storage, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /secrets&lt;/code&gt; — detects hardcoded credentials, API keys, passwords, and tokens in Terraform resource definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both endpoints accept a simple JSON payload &lt;code&gt;{"hcl": "&amp;lt;your terraform code&amp;gt;"}&lt;/code&gt; and return structured findings with severity levels, affected resources, and concrete remediation steps.&lt;/p&gt;

&lt;p&gt;The API is available on RapidAPI and requires no local installation. You can integrate it into any HTTP-capable environment — CI runners, pre-commit hooks, custom tooling, or a PR review bot.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Use Case: Security Group Misconfiguration
&lt;/h2&gt;

&lt;p&gt;Here is a Terraform resource that gets written more often than it should:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&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;Protocol &lt;code&gt;-1&lt;/code&gt; means all traffic. Ports &lt;code&gt;0-0&lt;/code&gt; means all ports. CIDR &lt;code&gt;0.0.0.0/0&lt;/code&gt; means the entire internet. This security group grants unrestricted inbound access from anywhere to anything.&lt;/p&gt;

&lt;p&gt;Send it to TerraGuard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://terraguard.p.rapidapi.com/analyze &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-RapidAPI-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"hcl": "resource \"aws_security_group\" \"web\" { ingress { from_port = 0 to_port = 0 protocol = \"-1\" cidr_blocks = [\"0.0.0.0/0\"] } }"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Security group allows unrestricted inbound traffic from any IP, posing a critical network exposure risk."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"risk_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CRITICAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issues"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CRITICAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NETWORK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Overly Permissive Security Group Ingress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The security group ingress rule allows all traffic (protocol '-1', ports 0-0) from any IP address (0.0.0.0/0), effectively exposing the associated resource to the entire internet without restriction."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_security_group.web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"recommendation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Restrict ingress rules to specific, necessary ports and protocols, and limit source CIDR blocks to trusted IP ranges (e.g., corporate VPN or specific services)."&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"passed_checks"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"total_issues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;Severity, category, affected resource, and a specific fix — all in one response.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Example: Hardcoded Secrets
&lt;/h2&gt;

&lt;p&gt;The second class of Terraform mistakes that regularly causes incidents is hardcoded credentials. Someone writes a quick prototype, the password ends up in the HCL, the HCL gets committed, and now the credential is in git history permanently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MySuperSecret123"&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send it to &lt;code&gt;POST /secrets&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://terraguard.p.rapidapi.com/secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-RapidAPI-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"hcl": "resource \"aws_db_instance\" \"db\" { password = \"MySuperSecret123\" username = \"admin\" }"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"secrets_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;"risk_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CRITICAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"findings"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CRITICAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hardcoded database password 'MySuperSecret123' found in aws_db_instance resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws_db_instance.db.password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"recommendation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Replace with variable reference (var.db_password), AWS Secrets Manager, or SSM Parameter Store"&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_findings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"remediation_summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Move all secrets to environment variables or a secrets manager."&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;h2&gt;
  
  
  Where to Plug This In
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CI/CD Pipelines
&lt;/h3&gt;

&lt;p&gt;Run both endpoints as a step before &lt;code&gt;terraform plan&lt;/code&gt;. If &lt;code&gt;risk_level&lt;/code&gt; is &lt;code&gt;CRITICAL&lt;/code&gt; or &lt;code&gt;total_issues&lt;/code&gt; is greater than zero, fail the pipeline. The structured JSON response makes it straightforward to write that condition in any scripting language or pipeline DSL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-Commit Hooks
&lt;/h3&gt;

&lt;p&gt;Use a tool like &lt;code&gt;pre-commit&lt;/code&gt; with a custom script that extracts staged &lt;code&gt;.tf&lt;/code&gt; files, sends them to TerraGuard, and blocks the commit if findings are returned. Developers get feedback before the code ever leaves their machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pull Request Reviews
&lt;/h3&gt;

&lt;p&gt;Integrate TerraGuard into your PR bot workflow. When a PR contains changes to &lt;code&gt;.tf&lt;/code&gt; files, automatically post the analysis results as a comment. Reviewers see the security posture of the change alongside the diff — no context switching required.&lt;/p&gt;

&lt;p&gt;This pairs naturally with diff-level analysis. If you're already using &lt;a href="https://rapidapi.com/edilluvio01/api/diffsense" rel="noopener noreferrer"&gt;DiffSense&lt;/a&gt; to analyze what changed between commits, you can use TerraGuard to analyze the security implications of those changes in parallel. DiffSense tells you &lt;em&gt;what&lt;/em&gt; changed; TerraGuard tells you whether &lt;em&gt;what changed&lt;/em&gt; is safe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;TerraGuard is available on RapidAPI:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://rapidapi.com/terrycrews99/api/terraguard" rel="noopener noreferrer"&gt;https://rapidapi.com/terrycrews99/api/terraguard&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free tier: 30 requests/month — enough to evaluate it in a real workflow&lt;/li&gt;
&lt;li&gt;No SDK or CLI required — any HTTP client works&lt;/li&gt;
&lt;li&gt;Endpoints: &lt;code&gt;POST /analyze&lt;/code&gt;, &lt;code&gt;POST /secrets&lt;/code&gt;, &lt;code&gt;GET /health&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Broader Point
&lt;/h2&gt;

&lt;p&gt;Static analysis for Terraform is not a new idea — tools like &lt;code&gt;tfsec&lt;/code&gt;, &lt;code&gt;checkov&lt;/code&gt;, and &lt;code&gt;terrascan&lt;/code&gt; exist and are worth knowing. The difference with an API-first approach is composability. You can call TerraGuard from anywhere that can make an HTTP request: a GitHub Action, a Slack bot, a custom dashboard, a VS Code extension, a Jupyter notebook running an infrastructure audit. There's no local runtime dependency to manage across different environments.&lt;/p&gt;

&lt;p&gt;If your team is already thinking about security at the code review and CI level — and you should be — adding a TerraGuard call to your Terraform workflow is a low-friction way to get structured, actionable findings without rearchitecting your toolchain.&lt;/p&gt;

&lt;p&gt;Catch the open security groups before they ship. It's one API call.&lt;/p&gt;

</description>
      <category>infrastructureascode</category>
      <category>security</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Using LLMs to do security analysis at the git diff level — what works, what doesn't, and why structured output matters</title>
      <dc:creator>DiffSense</dc:creator>
      <pubDate>Sun, 22 Mar 2026 04:15:33 +0000</pubDate>
      <link>https://dev.to/diffsense/using-llms-to-do-security-analysis-at-the-git-diff-level-what-works-what-doesnt-and-why-2a14</link>
      <guid>https://dev.to/diffsense/using-llms-to-do-security-analysis-at-the-git-diff-level-what-works-what-doesnt-and-why-2a14</guid>
      <description>&lt;p&gt;I've been experimenting with piping raw &lt;code&gt;git diff&lt;/code&gt; output into LLMs for automated security review, and I wanted to share what I've learned because some of the results surprised me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem that started this
&lt;/h2&gt;

&lt;p&gt;A teammate refactored a SQL query from string concatenation to an f-string. The diff looked like an improvement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-    query = "SELECT * FROM users WHERE id = " + user_id
&lt;/span&gt;&lt;span class="gi"&gt;+    query = f"SELECT * FROM users WHERE id = {user_id}"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three reviewers approved it. It looked cleaner. But the vulnerability was identical — both are injection vectors. The &lt;em&gt;cosmetic improvement&lt;/em&gt; actually made it harder to catch because it looked like the dev was modernizing the code.&lt;/p&gt;

&lt;p&gt;This is the kind of thing that made me think: can an LLM reliably detect that a diff &lt;em&gt;looks&lt;/em&gt; like a fix but &lt;em&gt;isn't&lt;/em&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;I built a FastAPI service that accepts a raw diff string and returns structured JSON with severity-classified security and quality issues. Here are the key design decisions I made and why.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why diffs and not full files?
&lt;/h3&gt;

&lt;p&gt;Full-file analysis is what SAST tools already do well. Diffs are interesting because they capture &lt;em&gt;intent&lt;/em&gt; — what the developer was trying to change. An LLM can reason about the gap between what a change &lt;em&gt;appears&lt;/em&gt; to do and what it &lt;em&gt;actually&lt;/em&gt; does. That's the niche.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why structured JSON output instead of free text?
&lt;/h3&gt;

&lt;p&gt;Because the output needs to be machine-consumable. If you want to post a PR comment, block a merge, or feed results into a dashboard, you need parseable severity levels and categories — not a paragraph of prose.&lt;/p&gt;

&lt;p&gt;The API has three endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /review&lt;/code&gt; — returns security and quality issues with severity levels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /commit-message&lt;/code&gt; — generates a Conventional Commits message from the diff&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /changelog&lt;/code&gt; — generates a Keep a Changelog formatted entry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what the &lt;code&gt;/review&lt;/code&gt; endpoint returns for the SQL injection example above:&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;"security_issues"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CRITICAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SQL query built with f-string interpolation is vulnerable to SQL injection. Use parameterized queries."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"line_hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE id = {user_id}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quality_issues"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Refactored from concatenation to f-string but the core vulnerability remains. This is a cosmetic change, not a security fix."&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Critical: SQL injection persists after refactor. The change is cosmetic."&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;Each issue has a severity (CRITICAL, HIGH, MEDIUM, LOW, INFO), a description, and a line hint pointing to the relevant code. The separation between security and quality issues lets you handle them differently in your pipeline — maybe security issues block the merge, but quality issues are just warnings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why DeepSeek instead of GPT-4?
&lt;/h3&gt;

&lt;p&gt;Cost. DeepSeek runs at roughly $0.0004 per request, which makes it viable to offer a free tier that's actually usable. I haven't done a rigorous side-by-side comparison, but anecdotally GPT-4 catches more subtle logic bugs while DeepSeek handles pattern-based security issues (injection, XSS, hardcoded secrets) well enough for a first-pass filter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why truncate diffs to 8,000 characters?
&lt;/h3&gt;

&lt;p&gt;Two reasons: (1) sending very large diffs produces worse analysis — the model loses focus when there's too much context, and (2) if your diff is that large, an automated tool probably isn't the right first step. The truncation is automatic — the API takes whatever you send and clips it if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it handles well
&lt;/h2&gt;

&lt;p&gt;Based on my testing so far, it reliably flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL injection (concatenation, f-strings, format strings)&lt;/li&gt;
&lt;li&gt;XSS via unsanitized DOM insertion (&lt;code&gt;innerHTML&lt;/code&gt;, template literals)&lt;/li&gt;
&lt;li&gt;Hardcoded secrets and API keys in source code&lt;/li&gt;
&lt;li&gt;Command injection (&lt;code&gt;shell=True&lt;/code&gt;, &lt;code&gt;os.system&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most interesting behavior is what I call the "cosmetic refactor" pattern — when a diff looks like it's fixing something but the underlying vulnerability is unchanged. The LLM seems to handle this well because it's reasoning about &lt;em&gt;what changed&lt;/em&gt; rather than just scanning for dangerous patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't catch (and probably can't)
&lt;/h2&gt;

&lt;p&gt;Being honest about the limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Business logic flaws&lt;/strong&gt; — the model has no context about what your app is supposed to do&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race conditions&lt;/strong&gt; — diffs don't contain concurrency context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency vulnerabilities&lt;/strong&gt; — it only sees your code changes, not your supply chain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subtle type confusion bugs&lt;/strong&gt; — LLMs aren't compilers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything requiring full codebase context&lt;/strong&gt; — the model only sees the diff, not the file it belongs to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means it won't catch a vulnerability that's introduced by the &lt;em&gt;interaction&lt;/em&gt; between the changed code and existing code it can't see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it fits in a pipeline
&lt;/h2&gt;

&lt;p&gt;This is a &lt;em&gt;pre-review filter&lt;/em&gt;, not a SAST replacement. Think of it as a layer that catches the obvious stuff before a human reviewer spends their time on it.&lt;/p&gt;

&lt;p&gt;Some ways you could use it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pre-commit hook&lt;/strong&gt;: run &lt;code&gt;git diff --cached&lt;/code&gt; through &lt;code&gt;/review&lt;/code&gt; before allowing a commit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD gate&lt;/strong&gt;: call &lt;code&gt;/review&lt;/code&gt; in a GitHub Action and post results as a PR comment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changelog automation&lt;/strong&gt;: call &lt;code&gt;/changelog&lt;/code&gt; on merge to auto-update CHANGELOG.md&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit message generation&lt;/strong&gt;: pipe your staged diff through &lt;code&gt;/commit-message&lt;/code&gt; to stop writing "fix stuff"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what a basic integration looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DIFF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git diff HEAD~1&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://diffsense.p.rapidapi.com/review &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-RapidAPI-Key: YOUR_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;diff&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$DIFF&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or in Python:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my.patch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://diffsense.p.rapidapi.com/review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&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;Content-Type&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="s"&gt;application/json&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="s"&gt;X-RapidAPI-Key&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="s"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&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;diff&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The bigger insight
&lt;/h2&gt;

&lt;p&gt;The most valuable thing I've learned building this: LLMs are better at analyzing &lt;em&gt;changes&lt;/em&gt; than analyzing &lt;em&gt;code&lt;/em&gt;. A diff is a narrative — "someone tried to do X" — and LLMs are good at narratives. Static code is just structure, and traditional tools handle structure better.&lt;/p&gt;

&lt;p&gt;That's why I think diff-level analysis is a genuine niche, not just "yet another AI code review tool." It's not competing with SAST — it's covering a different surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it / read the code
&lt;/h2&gt;

&lt;p&gt;The implementation is open source if you want to look at the FastAPI structure or the prompt engineering: &lt;a href="https://github.com/diffsense/diffsense-api" rel="noopener noreferrer"&gt;github.com/diffsense/diffsense-api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's also a hosted version on RapidAPI with a free tier (50 requests/month): &lt;a href="https://rapidapi.com/terrycrews99/api/diffsense" rel="noopener noreferrer"&gt;rapidapi.com/terrycrews99/api/diffsense&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm actively developing this and would genuinely appreciate feedback — what patterns would you want caught at the diff level? What would make this useful enough to add to your pipeline?&lt;/p&gt;

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