<?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: CopperSunDev</title>
    <description>The latest articles on DEV Community by CopperSunDev (@coppersundev).</description>
    <link>https://dev.to/coppersundev</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%2F3659025%2F67b7af33-5040-4848-9b99-f2b9ccf2e6c3.png</url>
      <title>DEV Community: CopperSunDev</title>
      <link>https://dev.to/coppersundev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/coppersundev"/>
    <language>en</language>
    <item>
      <title>What Your Auditor Wants From Your AI Codebase</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:55:11 +0000</pubDate>
      <link>https://dev.to/coppersundev/what-your-auditor-wants-from-your-ai-codebase-21kh</link>
      <guid>https://dev.to/coppersundev/what-your-auditor-wants-from-your-ai-codebase-21kh</guid>
      <description>&lt;p&gt;A SOC 2 auditor reviewing your AI-augmented engineering team asks one question first: how do you know what's in the code? If the answer involves a stochastic scanner whose output varies between runs, the audit has a problem. If the answer involves a deterministic scanner whose output is reproducible against a pinned commit, the audit has a clean control.&lt;/p&gt;

&lt;p&gt;BrassCoders, the bug scanner for AI coders, is the deterministic scanner. This piece walks through the audit posture an AI-augmented engineering team needs by 2026 — the SOC 2 controls BrassCoders maps to, what the YAML looks like to an auditor, and what BrassCoders is honestly not audited for.&lt;/p&gt;

&lt;p&gt;This is the sixth piece in BrassCoders's &lt;a href="https://coppersun.dev/ai-blind-spots/" rel="noopener noreferrer"&gt;AI Coding Assistant Blind Spots&lt;/a&gt; pillar — the audit answer to the technical coverage map laid out in the rest of the series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Auditors Reject Stochastic Output
&lt;/h2&gt;

&lt;p&gt;BrassCoders detection output is bit-for-bit reproducible against a pinned commit, by design. The OSS core runs the same 12 scanners with the same configuration on the same input and produces the same finding list every time. Auditors accept this; auditors reject stochastic scanners that produce different findings on different runs.&lt;/p&gt;

&lt;p&gt;The auditor's logic. A control trail in SOC 2 needs evidence. Evidence has to be reproducible — the auditor needs to be able to re-run the check and see the same result. A scanner whose output varies between runs because the model temperature changed, because the training data refreshed, because the prompt template was tuned, cannot produce an audit trail. The auditor cannot tell which version of the scanner ran when the artifact was generated; the auditor cannot reproduce the artifact.&lt;/p&gt;

&lt;p&gt;LLM-based PR reviewers are stochastic. Same input, different output. Useful for engineering, problematic for audit. Most teams that adopt LLM-based PR review keep the existing deterministic SAST around for the audit trail — even if the SAST finds fewer real bugs than the LLM, the SAST findings are evidence-grade and the LLM findings are not.&lt;/p&gt;

&lt;p&gt;BrassCoders ships both layers and keeps them separate. The deterministic scanner layer (the OSS core, plus the same scanners in Paid) is the audit-grade evidence. The AI-powered enrichment layer (Paid only) is the developer productivity layer. The two layers serve different stakeholders. The auditor reads the scanner layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SOC 2 Controls BrassCoders Maps To
&lt;/h2&gt;

&lt;p&gt;BrassCoders maps cleanly to two SOC 2 Trust Services Criteria: CC8.1 (change management — every change is scanned) and CC7.1 (system operations — vulnerabilities are monitored). The mapping is direct enough that auditors recognize the artifact without extensive explanation.&lt;/p&gt;

&lt;p&gt;CC8.1 — Change Management. The control requires the entity to authorize, design, develop, test, approve, and implement changes to infrastructure, data, software, and procedures. The BrassCoders scan output, persisted as a CI artifact tied to the commit hash, evidences the "test" and "approve" steps. Every pull request gets scanned; every scan produces a YAML; every YAML lives in the build artifact store. The auditor walks the trail.&lt;/p&gt;

&lt;p&gt;CC7.1 — System Operations. The control requires the entity to monitor system components and operations for anomalies that are indicative of malicious acts, natural disasters, and errors. The BrassCoders scan history, retained per release, evidences ongoing vulnerability monitoring. The auditor reviewing CC7.1 sees a continuous trail of vulnerability detection runs against the production codebase.&lt;/p&gt;

&lt;p&gt;The mapping for adjacent frameworks. HIPAA 164.308(a)(1) (security management process): same evidence applies. ISO 27001 A.12.6.1 (technical vulnerability management): same evidence. PCI-DSS requirement 6.6 (review of public-facing applications for vulnerabilities): applicable for the AppSec scope. The same scan trail covers multiple frameworks because the underlying control is the same — continuous deterministic vulnerability monitoring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.aicpa-cima.com/resources/landing/system-and-organization-controls-soc-suite-of-services" rel="noopener noreferrer"&gt;AICPA's Trust Services Criteria&lt;/a&gt; defines the SOC 2 controls. The mapping above is BrassCoders's reading; your auditor will translate to the specific control language your firm uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducibility As An Audit Artifact
&lt;/h2&gt;

&lt;p&gt;BrassCoders treats reproducibility as a load-bearing feature, not an implementation detail. The reproducible-benchmarks page ships scan results against nine open-source codebases with pinned commit hashes; any auditor or engineer can re-run the scan and confirm the same output.&lt;/p&gt;

&lt;p&gt;The benchmarks page is at &lt;a href="https://coppersun.dev/benchmarks/" rel="noopener noreferrer"&gt;coppersun.dev/benchmarks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The mechanic. BrassCoders pins every scanner version in &lt;code&gt;pyproject.toml&lt;/code&gt;. Pylint, Bandit, Semgrep, detect-secrets — each one is locked to a specific minor version. Pyre is locked to a narrow window because the Pysa model format has been unstable across minors. The version pinning is intentional; bumping a scanner version is a calibration event that requires re-verifying the bundled detection model.&lt;/p&gt;

&lt;p&gt;The reproducibility checklist for an audit. (1) Customer pins BrassCoders version in their lockfile. (2) Customer pins the project commit hash. (3) Customer runs &lt;code&gt;brasscoders scan&lt;/code&gt; and persists the output as a CI artifact. (4) Auditor runs the same BrassCoders version against the same commit and confirms the output matches. The check completes in minutes against typical codebases.&lt;/p&gt;

&lt;p&gt;The honest scope. Reproducibility is bit-exact at the scanner layer. The Paid plan's AI-powered enrichment layer is deterministic at the scanner layer but stochastic at the embedding layer — the same scanner findings can produce slightly different rankings across runs because embedding models do not promise bit-exact output. For audit purposes, the underlying scanner findings are what matters, and those are reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The YAML Looks Like To An Auditor
&lt;/h2&gt;

&lt;p&gt;BrassCoders writes the scan output as YAML in &lt;code&gt;.brass/ai_instructions.yaml&lt;/code&gt;, designed to be human-readable by developers and machine-readable by AI assistants. Auditors read the same file the AI does; the structure is stable, the fields are documented, the schema lives in the BrassCoders repo.&lt;/p&gt;

&lt;p&gt;The schema in summary. The top-level YAML contains a &lt;code&gt;metadata&lt;/code&gt; section (scan timestamp, BrassCoders version, scanner versions, project root path), a &lt;code&gt;critical_issues&lt;/code&gt; array (CRITICAL-severity findings, with file path, line number, finding type, and description per entry), a &lt;code&gt;findings&lt;/code&gt; array (all other findings, sorted by relevance), and a &lt;code&gt;summary&lt;/code&gt; section (counts by severity, counts by scanner, total finding count). The schema is documented at &lt;a href="https://coppersun.dev/legal/privacy" rel="noopener noreferrer"&gt;/legal/privacy&lt;/a&gt; for what the scanner persists and at the BrassCoders GitHub repo for the full structure.&lt;/p&gt;

&lt;p&gt;The audit-relevant section. Auditors reviewing CC8.1 walk the &lt;code&gt;critical_issues&lt;/code&gt; array per release. The expectation is that critical findings are addressed before release, not that critical findings are zero. The audit trail is: critical_issues at scan time → ticket opened → ticket closed → next scan shows critical_issues addressed. The YAML supports the trail.&lt;/p&gt;

&lt;p&gt;What the YAML deliberately does not contain. No raw source code. No PII matched values (the privacy scanner redacts before serialization). No secret values (the secrets scanner records type and hash, never the secret itself). The two-boundary redaction policy is documented in the privacy disclosure. Auditors who confirm the redaction policy confirm that the scan output is safe to retain as a build artifact indefinitely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What BrassCoders Isn't Audited For (Honest Scope)
&lt;/h2&gt;

&lt;p&gt;BrassCoders is not a third-party-audited security service, and any claim suggesting otherwise would be inaccurate. The OSS core is a local CLI with no service surface; there is no entity to audit. The Paid plan's gateway is a thin proxy in front of a hosted embedding model; the gateway logic is documented but not third-party attested.&lt;/p&gt;

&lt;p&gt;What this means in practice. A customer adopting BrassCoders for SOC 2 evidence uses BrassCoders as a deterministic vulnerability scanner. The deterministic detection layer is the evidence. The customer's own auditors confirm the deterministic behavior by reproducing scans against pinned commits. The customer does not point at a BrassCoders-issued SOC 2 report because there is none.&lt;/p&gt;

&lt;p&gt;What BrassCoders provides instead. Full documentation of the data plane in &lt;a href="https://coppersun.dev/legal/privacy" rel="noopener noreferrer"&gt;/legal/privacy&lt;/a&gt;. Reproducible benchmarks at &lt;a href="https://coppersun.dev/benchmarks/" rel="noopener noreferrer"&gt;/benchmarks&lt;/a&gt;. Open source code at the detection layer (Apache 2.0). Each of these is independently verifiable by the customer or their auditor. The verifiability replaces the third-party attestation; the auditor can confirm directly rather than rely on a report.&lt;/p&gt;

&lt;p&gt;When BrassCoders will pursue SOC 2. The honest answer is when customers ask in writing. The cost of a SOC 2 Type II audit is roughly $50,000-$100,000 for an early-stage SaaS; BrassCoders will pursue the audit when paying customers cite it as a procurement blocker. As of mid-2026, the deterministic-by-design and source-available posture has been sufficient evidence for the customers in our pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;BrassCoders ships deterministic, reproducible vulnerability detection across 12 scanners, packaged as YAML output that an auditor reads the same way an AI assistant does. The audit posture is the byproduct of the deterministic-by-design architecture; it is not a separate feature.&lt;/p&gt;

&lt;p&gt;Install with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;brasscoders
brasscoders scan /path/to/project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Persist &lt;code&gt;.brass/ai_instructions.yaml&lt;/code&gt; as a CI artifact. Tie the artifact to the commit hash. When the auditor asks how you know what's in your code, point at the artifact.&lt;/p&gt;

&lt;p&gt;The full coverage map of what BrassCoders detects — and what it does not — is in the &lt;a href="https://coppersun.dev/ai-blind-spots/" rel="noopener noreferrer"&gt;AI Coding Assistant Blind Spots&lt;/a&gt; pillar. The seven blind-spot categories, the seven detector mappings, and the honest scope of each detector are documented there.&lt;/p&gt;

</description>
      <category>security</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Five FastAPI Security Patterns AI Coders Get Wrong</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:55:06 +0000</pubDate>
      <link>https://dev.to/coppersundev/five-fastapi-security-patterns-ai-coders-get-wrong-39gc</link>
      <guid>https://dev.to/coppersundev/five-fastapi-security-patterns-ai-coders-get-wrong-39gc</guid>
      <description>&lt;p&gt;AI coding assistants generate working FastAPI endpoints in minutes. They also generate the same five security mistakes, reproducibly, across codebases. Not because FastAPI is insecure — FastAPI's official security documentation covers parameterized queries and authentication patterns clearly. The mistakes come from training data. The AI learned from millions of tutorial examples and StackOverflow answers, many of which use insecure patterns for brevity. The AI predicts what comes next, and what comes next in a tutorial is often &lt;code&gt;f"SELECT * FROM users WHERE id = {user_id}"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;BrassCoders detects all five patterns below in a single pass using its OSS-core scanners: Bandit, Yelp's detect-secrets, and three custom detectors for AI-introduced anti-patterns. No paid plan required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI Assistants Produce These Patterns
&lt;/h2&gt;

&lt;p&gt;BrassCoders was built on a specific observation: AI coding assistants are pattern-completion engines, and the patterns they've seen most often are not always the patterns that are correct. Training data for Python web APIs skews toward tutorials and quick-start examples — code written to be readable, not to be secure. A StackOverflow answer that demonstrates a FastAPI route with an f-string SQL query gets upvoted for clarity. That same code, reproduced at scale by an AI assistant, ships a SQL injection vulnerability into production.&lt;/p&gt;

&lt;p&gt;This is structural. Prompting your way around it helps at the margins. The more reliable fix is a scanner that runs after generation and flags the specific patterns that AI assistants get wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1 — SQL Injection via f-String
&lt;/h2&gt;

&lt;p&gt;BrassCoders detects SQL queries constructed via string formatting using Bandit rule B608, which fires on any detected SQL statement where user-controlled values are interpolated directly into the query string.&lt;/p&gt;

&lt;p&gt;The generated code looks like this:&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;databases&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://localhost/mydb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.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;/users/{user_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;query&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 * FROM users WHERE id = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem: &lt;code&gt;user_id&lt;/code&gt; is typed as &lt;code&gt;int&lt;/code&gt; by FastAPI, which blocks the most obvious string-injection payloads — but it doesn't block all of them. More critically, this pattern teaches the AI (and the developer reading it) that f-strings in SQL are acceptable. The same pattern, copy-pasted to a string-typed field, becomes a textbook SQL injection. OWASP API Security Top 10 lists injection as &lt;a href="https://owasp.org/www-project-api-security/" rel="noopener noreferrer"&gt;API8:2023&lt;/a&gt;, appearing across database queries, shell commands, and template engines.&lt;/p&gt;

&lt;p&gt;The fix is parameterized queries:&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="nd"&gt;@app.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;/users/{user_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users WHERE id = :user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&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;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BrassCoders catches the f-string version via Bandit B608. The parameterized version produces no finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2 — Hardcoded Credentials in Config
&lt;/h2&gt;

&lt;p&gt;BrassCoders detects hardcoded credentials — API keys, database connection strings, signing secrets — using Yelp's detect-secrets library, which covers 20+ credential formats including OpenAI keys, AWS access keys, GitHub PATs, Stripe live keys, and high-entropy strings.&lt;/p&gt;

&lt;p&gt;The generated code looks like this:&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="c1"&gt;# config.py
&lt;/span&gt;&lt;span class="n"&gt;DATABASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://admin:hunter2@localhost/mydb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;OPENAI_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk-proj-abc123...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-super-secret-key-that-nobody-will-ever-guess&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern appears constantly in AI-generated code. The AI is optimizing for "give the developer a working example" and hardcodes placeholder values it doesn't expect to be real. Developers copy the config, swap in real values, and commit it. The credential is now in git history permanently, regardless of whether the file is later removed.&lt;/p&gt;

&lt;p&gt;The fix moves all secrets to environment variables:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lru_cache&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseSettings&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseSettings&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;database_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;openai_api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;env_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@lru_cache&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_settings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FastAPI's official &lt;a href="https://fastapi.tiangolo.com/tutorial/security/" rel="noopener noreferrer"&gt;security documentation&lt;/a&gt; recommends environment-based configuration for all secrets. BrassCoders catches the hardcoded version via detect-secrets. The &lt;code&gt;BaseSettings&lt;/code&gt; pattern produces no finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3 — Shell Injection in Report Runners
&lt;/h2&gt;

&lt;p&gt;BrassCoders detects &lt;code&gt;subprocess&lt;/code&gt; calls with &lt;code&gt;shell=True&lt;/code&gt; via Bandit rules B602 and B603, which fire whenever user-controlled data flows into a shell command string.&lt;/p&gt;

&lt;p&gt;The generated code looks like this:&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.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;/reports/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report_type&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;output_format&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;cmd&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;generate_report --type &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;report_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; --format &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_format&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&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;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A request with &lt;code&gt;report_type=pdf; rm -rf /&lt;/code&gt; executes both commands. The &lt;code&gt;shell=True&lt;/code&gt; flag tells the OS to parse the string through &lt;code&gt;/bin/sh&lt;/code&gt;, which interprets shell metacharacters. Any user-controlled value in that string becomes arbitrary code execution. &lt;a href="https://bandit.readthedocs.io/en/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html" rel="noopener noreferrer"&gt;Bandit's B602 documentation&lt;/a&gt; flags this as HIGH severity.&lt;/p&gt;

&lt;p&gt;The fix passes arguments as a list, bypassing the shell entirely:&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;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shlex&lt;/span&gt;

&lt;span class="n"&gt;ALLOWED_TYPES&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;pdf&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;csv&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;html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;ALLOWED_FORMATS&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;letter&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;a4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.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;/reports/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report_type&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;output_format&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;report_type&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;ALLOWED_TYPES&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;output_format&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;ALLOWED_FORMATS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid report parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&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;generate_report&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;--type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_format&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&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;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;shell=True&lt;/code&gt;. No string interpolation. BrassCoders catches the original via B602/B603 and flags it HIGH.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4 — Hallucinated Middleware Package
&lt;/h2&gt;

&lt;p&gt;BrassCoders's AI-pattern scanner flags imports of packages that don't exist on PyPI, catching a class of AI-generated bug where the model invents a plausible-sounding package name that has never been published.&lt;/p&gt;

&lt;p&gt;The generated code looks like this:&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi_auth_middleware&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AuthMiddleware&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi_rate_limiter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RateLimiterMiddleware&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi_security_headers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SecurityHeadersMiddleware&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-secret-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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RateLimiterMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SecurityHeadersMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of these packages exist; some don't; some exist but with different import paths or class names. The AI generates what sounds like it should exist based on patterns it's seen. The code passes a linter because Python import errors are runtime — not compile-time. The developer's IDE may autocomplete based on stubs that don't match the real package API.&lt;/p&gt;

&lt;p&gt;The danger goes beyond a runtime &lt;code&gt;ModuleNotFoundError&lt;/code&gt;. A package name that doesn't exist on PyPI is an unclaimed namespace. An attacker can publish a malicious package under that name — a technique known as dependency confusion or slopsquatting. Any developer who runs &lt;code&gt;pip install fastapi_auth_middleware&lt;/code&gt; expecting the AI-suggested package installs the attacker's code instead.&lt;/p&gt;

&lt;p&gt;The fix is to verify every package against PyPI before using it, use packages with established maintenance histories, and let a scanner flag unresolvable imports before they reach production. BrassCoders's phantom-API detector cross-references imports against PyPI and flags packages that don't resolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 5 — O(N²) Response Construction
&lt;/h2&gt;

&lt;p&gt;BrassCoders's performance scanner flags two specific O(N²) patterns in Python: &lt;code&gt;results += [item]&lt;/code&gt; inside a loop and &lt;code&gt;list.insert(0, item)&lt;/code&gt; inside a loop. Both appear consistently in AI-generated list-building code.&lt;/p&gt;

&lt;p&gt;The generated code looks like this:&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.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;/users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_users&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;Session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;users&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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;results&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the reversed-list variant:&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;ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;results += [item]&lt;/code&gt; creates a new list on every iteration. &lt;code&gt;insert(0, ...)&lt;/code&gt; shifts every existing element right on every iteration. On a database returning 10 rows, neither matters. On a database returning 100,000 rows — not unusual for a users table or an events feed — both exhibit quadratic time and memory behavior. The &lt;code&gt;insert(0, ...)&lt;/code&gt; variant is particularly slow: it's O(N) per call, O(N²) total.&lt;/p&gt;

&lt;p&gt;The fixes are straightforward:&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="c1"&gt;# Fix for += pattern
&lt;/span&gt;&lt;span class="n"&gt;results&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Fix for insert(0, ...) pattern — reverse at the query level
&lt;/span&gt;&lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&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;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asc&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="c1"&gt;# Or, if you must reverse in Python:
&lt;/span&gt;&lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# O(N) single reversal, not O(N²) cumulative shifts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BrassCoders's performance scanner catches both &lt;code&gt;+= [item]&lt;/code&gt; and &lt;code&gt;insert(0, ...)&lt;/code&gt; inside loops and reports them as performance findings in the YAML output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running BrassCoders on a FastAPI Project
&lt;/h2&gt;

&lt;p&gt;BrassCoders covers all five patterns above in a single offline scan, combining Bandit security rules, Yelp's detect-secrets credential patterns, and three custom BrassCoders detectors for AI-introduced anti-patterns. The scan output is &lt;code&gt;.brass/ai_instructions.yaml&lt;/code&gt;, a ranked findings list formatted for direct paste into Claude Code or Cursor.&lt;/p&gt;

&lt;p&gt;Install and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;brasscoders
brasscoders &lt;span class="nt"&gt;--offline&lt;/span&gt; scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--offline&lt;/code&gt; flag enforces zero outbound network calls. The OSS core is Apache 2.0 licensed — no account required, no telemetry.&lt;/p&gt;

&lt;p&gt;For larger projects, BrassCoders Paid ($12/month) adds a semantic deduplication pass that drops near-duplicate findings across files, reducing the scan output further before triage. Activate it with &lt;code&gt;brasscoders activate&lt;/code&gt; after subscribing at &lt;a href="https://coppersun.dev/pricing" rel="noopener noreferrer"&gt;coppersun.dev/pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The five patterns above are AI-specific. They're not FastAPI bugs and they're not developer errors — they're what happens when a model trained on tutorial data generates production code without a scanner in the loop.&lt;/p&gt;

&lt;p&gt;Add the scanner.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>When AI Invents Libraries: Detecting Hallucinated Imports</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:54:39 +0000</pubDate>
      <link>https://dev.to/coppersundev/when-ai-invents-libraries-detecting-hallucinated-imports-2l1p</link>
      <guid>https://dev.to/coppersundev/when-ai-invents-libraries-detecting-hallucinated-imports-2l1p</guid>
      <description>&lt;p&gt;BrassCoders's OSS core scans for hallucinated package imports with a single flag: &lt;code&gt;brasscoders scan --check-package-hallucination&lt;/code&gt;. The flag is opt-in because the check is the only path in the OSS core that makes outbound network calls. The pattern matters because AI coding assistants generate imports of packages that don't exist at rates measured by published research, and a typosquatter who registers those hallucinated names as malware turns the hallucination into a supply-chain attack.&lt;/p&gt;

&lt;p&gt;This post walks through why hallucinated imports are a different kind of failure than other AI mistakes, what the published research says about how often they happen, how the typosquat attack chain works in practice, and how BrassCoders's hallucination check fits into a CI workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Hallucinated Import
&lt;/h2&gt;

&lt;p&gt;BrassCoders treats a hallucinated import as a finding type that requires its own detector: an &lt;code&gt;import&lt;/code&gt; or &lt;code&gt;require&lt;/code&gt; statement referencing a package name that doesn't resolve on the relevant registry. The AI writes &lt;code&gt;from fastapi_users_pydantic import UserManager&lt;/code&gt; when only &lt;code&gt;fastapi-users&lt;/code&gt; is published on PyPI; the syntax is valid; the IDE doesn't complain; the failure surfaces at install time or — much worse — when a typosquatter has registered the hallucinated name as a malware-bearing package.&lt;/p&gt;

&lt;p&gt;Why the hallucination happens: LLMs generate plausible-sounding package names by pattern-matching against the millions of imports they saw during training. A model that's seen &lt;code&gt;fastapi-users&lt;/code&gt; 50,000 times and &lt;code&gt;pydantic&lt;/code&gt; 200,000 times can confidently produce &lt;code&gt;fastapi_users_pydantic&lt;/code&gt; as a "combined" package because the structural pattern looks correct. The model has no grounding in whether the combined package actually exists; it's just producing the most-likely next token given the prompt.&lt;/p&gt;

&lt;p&gt;Why static analyzers miss it: traditional Python linters check syntax, type annotations, and code style. None of them verify that an imported name corresponds to a published package on PyPI. &lt;a href="https://github.com/pylint-dev/pylint" rel="noopener noreferrer"&gt;Pylint&lt;/a&gt; doesn't. &lt;a href="https://github.com/PyCQA/bandit" rel="noopener noreferrer"&gt;Bandit&lt;/a&gt; doesn't. &lt;a href="https://github.com/PyCQA/pyflakes" rel="noopener noreferrer"&gt;Pyflakes&lt;/a&gt; doesn't. The verification requires a network call, and most linters are explicitly offline tools.&lt;/p&gt;

&lt;p&gt;BrassCoders surfaces hallucinated imports as a security finding, severity high, with the package name and the source line. The downstream AI consumer (or human reviewer) then decides whether to replace the import with a real package, remove the code entirely, or accept the risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Often Models Hallucinate Packages
&lt;/h2&gt;

&lt;p&gt;BrassCoders's position on hallucinated-package rates: the absolute percentage depends on the model and the benchmark, but the rate is high enough across published research that any team relying on AI-generated code without an existence-verification step is shipping a real risk. The specific numbers shift as models improve and as benchmarks change; the pattern is durable.&lt;/p&gt;

&lt;p&gt;The most-cited research is &lt;a href="https://www.lasso.security/" rel="noopener noreferrer"&gt;Lasso Security&lt;/a&gt;'s 2024 analysis of LLM-generated package suggestions. Their methodology: prompt commercial code-completion models with realistic refactoring tasks, capture every imported package, and check each one against the relevant registry. The findings document hallucinated-package generation as a consistent failure mode across major models, with rates measurable enough to constitute a category of risk rather than an occasional bug.&lt;/p&gt;

&lt;p&gt;The rate is higher in some contexts than others. Long-context refactoring (where the model fills in plausible-adjacent libraries from training memory) tends to produce more hallucinations than short single-file completions. Less-popular languages and frameworks produce higher rates than mainstream ones because the model has less ground-truth coverage. Newer libraries are more dangerous than older ones because the model's training data lags reality.&lt;/p&gt;

&lt;p&gt;What the research doesn't tell you: the rate at your specific company, on your specific codebase, with your specific prompt practices. The only way to know is to instrument your AI-augmented PR pipeline with a hallucination check and measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Hallucinated Imports Are a Supply-Chain Risk
&lt;/h2&gt;

&lt;p&gt;BrassCoders treats hallucinated imports as supply-chain vulnerabilities, not stylistic errors. The attack chain is straightforward: a malicious actor monitors AI-generated code (via published GitHub repositories, AI-tool usage research, or direct model behavior testing), identifies frequently-hallucinated package names, registers those names as typosquatting packages on PyPI or npm with embedded malware, and waits. When AI-generated code reaches a developer's environment or a CI runner that does &lt;code&gt;pip install&lt;/code&gt; or &lt;code&gt;npm install&lt;/code&gt;, the typosquat package gets fetched and its install-time hooks execute.&lt;/p&gt;

&lt;p&gt;This isn't theoretical. &lt;a href="https://pypi.org/security/" rel="noopener noreferrer"&gt;PyPI's security advisory feed&lt;/a&gt; and the &lt;a href="https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" rel="noopener noreferrer"&gt;npm security advisories database&lt;/a&gt; both track typosquatting as one of the largest attack surfaces in their ecosystems, with hundreds of confirmed malicious-package incidents per year predating AI tools. AI hallucinations accelerate the attack surface by providing the typosquatter with target names — instead of guessing what common typos developers make, the attacker watches the LLMs themselves and registers the names the LLMs invent.&lt;/p&gt;

&lt;p&gt;The malware delivery, once you accept the attacker has the package up, can be anything that runs at install time: credential exfiltration from &lt;code&gt;~/.aws/credentials&lt;/code&gt; or &lt;code&gt;~/.ssh/&lt;/code&gt;, persistent backdoors, cryptominers, ransomware seeds. The Python ecosystem's &lt;code&gt;setup.py&lt;/code&gt; and the JavaScript ecosystem's &lt;code&gt;preinstall&lt;/code&gt;/&lt;code&gt;install&lt;/code&gt; hooks both run arbitrary code at install time by design, and there's no sandboxing. Snyk's &lt;a href="https://security.snyk.io/" rel="noopener noreferrer"&gt;Vulnerability Database&lt;/a&gt; maintains an actively-updated catalog of confirmed malicious packages — a useful reality check for any team that hasn't yet wired up supply-chain scanning.&lt;/p&gt;

&lt;p&gt;Mitigation requires catching the hallucination before the install. By the time &lt;code&gt;pip install&lt;/code&gt; runs, the attacker has already won — running the hallucination check at code review time (or as a pre-commit hook) is the only reliable defense.&lt;/p&gt;

&lt;h2&gt;
  
  
  How BrassCoders's Check Works
&lt;/h2&gt;

&lt;p&gt;BrassCoders's package-hallucination check parses every import statement in your scanned files, extracts the package name, and queries the target registry — PyPI's JSON API at &lt;code&gt;pypi.org/pypi//json&lt;/code&gt;, npm's registry at &lt;code&gt;registry.npmjs.org/&lt;/code&gt;, or Go's &lt;code&gt;pkg.go.dev/&lt;/code&gt; — to verify the package exists. A non-200 response is a hallucination signal; a 200 means the package is registered on the registry.&lt;/p&gt;

&lt;p&gt;The check is opt-in via the &lt;code&gt;--check-package-hallucination&lt;/code&gt; CLI flag because it's the only path in the OSS core that makes outbound network calls. BrassCoders's default posture is offline-first: scans complete with zero network traffic unless you explicitly turn on this one check. The flag respects &lt;code&gt;--offline&lt;/code&gt;: passing &lt;code&gt;--offline&lt;/code&gt; overrides the opt-in back to off.&lt;/p&gt;

&lt;p&gt;What gets sent over the wire: the bare package name (&lt;code&gt;fastapi-users&lt;/code&gt;, &lt;code&gt;lodash&lt;/code&gt;, &lt;code&gt;github.com/spf13/cobra&lt;/code&gt;). No source code, no surrounding context, no project name, no telemetry. PyPI sees nothing different from a normal &lt;code&gt;pip search&lt;/code&gt; query.&lt;/p&gt;

&lt;p&gt;What gets returned: a JSON object from PyPI or npm if the package exists, a 404 if it doesn't. BrassCoders discards the JSON content and only retains the existence signal. The package name plus the existence boolean are what go into the finding record.&lt;/p&gt;

&lt;p&gt;What happens with private packages: if your code imports an internal package that isn't published to the public registry (e.g. &lt;code&gt;mycompany_auth&lt;/code&gt;), the check will flag it as hallucinated. This is a known false-positive pattern. Mitigation: pass &lt;code&gt;--internal-packages mycompany_auth,mycompany_billing&lt;/code&gt; to whitelist names the check should skip. Or simply don't enable the check on repos that import a lot of private packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use It in CI
&lt;/h2&gt;

&lt;p&gt;BrassCoders's hallucination check fits into a CI pipeline as a pre-merge gate. Run &lt;code&gt;brasscoders scan --check-package-hallucination&lt;/code&gt; against your branch and fail the build if any finding type matches &lt;code&gt;HALLUCINATED_IMPORT&lt;/code&gt;. The added latency is 100-500ms per imported package depending on registry network latency, which usually adds 5-30 seconds to a typical scan.&lt;/p&gt;

&lt;p&gt;A minimal GitHub Actions workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BrassCoders scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.12'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pipx install brasscoders&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders scan --check-package-hallucination .&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;if grep -q "HALLUCINATED_IMPORT" .brass/security_report.yaml; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "Hallucinated package import detected. Fail the build."&lt;/span&gt;
            &lt;span class="s"&gt;exit 1&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same shape works in GitLab CI, CircleCI, Jenkins, or any other runner — the only requirement is a Python 3.10+ environment and network access to PyPI/npm. The check shouldn't run inside an air-gapped environment; pair it with regular &lt;code&gt;brasscoders scan&lt;/code&gt; (without the flag) for those cases.&lt;/p&gt;

&lt;p&gt;What about catching hallucinations at code-generation time, before they hit a PR at all? That's the right end-state. Until your AI assistant of choice runs hallucination verification on its own output (and most don't yet), the pre-merge scan is the load-bearing defense.&lt;/p&gt;




&lt;p&gt;The supply-chain attack chain that hallucinated imports enable is the kind of thing that gets a security org calling at 11pm. The mitigation is a one-line CI step. The trade-off isn't close.&lt;/p&gt;

&lt;p&gt;Install BrassCoders with &lt;code&gt;pipx install brasscoders&lt;/code&gt; and add the check to your PR pipeline. For the broader context on AI code review failure modes, see &lt;a href="https://dev.to/ai-code-review-guide/"&gt;AI Code Review: The Practical Guide for 2026&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>engineering</category>
    </item>
    <item>
      <title>The Secrets Your AI Assistant Might Leak (And How to Catch Them)</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:54:34 +0000</pubDate>
      <link>https://dev.to/coppersundev/the-secrets-your-ai-assistant-might-leak-and-how-to-catch-them-585l</link>
      <guid>https://dev.to/coppersundev/the-secrets-your-ai-assistant-might-leak-and-how-to-catch-them-585l</guid>
      <description>&lt;p&gt;BrassCoders detects around 20 canonical secret formats — AWS access keys, GitHub personal access tokens, OpenAI keys, Stripe live keys, JWT tokens, PEM-encoded private keys, Anthropic keys, Slack tokens, NPM auth tokens, SendGrid keys, Mailgun keys, DigitalOcean tokens, Twilio credentials — plus a high-entropy fallback that catches the formats it hasn't seen before. The reason this matters: AI coding assistants embed secret-shaped strings in generated config files, example scripts, and test fixtures more often than developers expect, and the secret in your AI-generated &lt;code&gt;.env.example&lt;/code&gt; looks just like a real secret to a downstream attacker.&lt;/p&gt;

&lt;p&gt;This post covers why AI tools generate credential-shaped output, what specific formats are worth scanning for, how entropy-based detection catches the formats nobody has cataloged yet, and how BrassCoders's two-boundary redaction prevents the detector itself from becoming a leak surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI Generates Code With Embedded Secrets
&lt;/h2&gt;

&lt;p&gt;BrassCoders treats embedded-secret hallucinations as a known AI failure mode, not an edge case. Models that have seen millions of code examples in training have also seen millions of API keys, tokens, and passwords embedded in those examples; in the absence of an explicit safety policy at generation time, the model reproduces credential-shaped strings as plausible filler when generating config files, example scripts, or test fixtures.&lt;/p&gt;

&lt;p&gt;The pattern happens in three recurring contexts. First, &lt;strong&gt;example code generation&lt;/strong&gt;: ask the AI for "an example .env file for a Stripe + SendGrid + AWS setup" and the model produces a file with what look like Stripe live keys (starting &lt;code&gt;sk_live_&lt;/code&gt;), SendGrid keys (starting &lt;code&gt;SG.&lt;/code&gt;), and AWS access keys (starting &lt;code&gt;AKIA&lt;/code&gt;). The keys are syntactically valid — the model knows the format perfectly because it saw thousands of real ones in training — and a junior developer might commit the file without realizing the AI invented credentials that look real enough to fool security automation.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;test fixture generation&lt;/strong&gt;: ask the AI to "write a test that verifies authentication" and the model fabricates a JWT token, a PEM-encoded RSA private key, or a session cookie value. The fabricated credentials are usually high-entropy and structurally correct. Some get committed to repos as "test data" and end up triggering security scanners weeks later.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;completion-context leakage&lt;/strong&gt;: an AI assistant operating on a partial file may complete a config line based on patterns it saw nearby. If your real &lt;code&gt;.env&lt;/code&gt; is open in the same editor session and the AI has context, the completion may include credential-shaped strings borrowed from that context — sometimes the real credentials, sometimes a hallucinated variant that's been altered just enough to look plausible. &lt;a href="https://www.gitguardian.com/state-of-secrets-sprawl-report-2024" rel="noopener noreferrer"&gt;GitGuardian&lt;/a&gt;'s annual State of Secrets Sprawl report has tracked AI tooling as a contributing pattern in the growing rate of public-repo secret leaks.&lt;/p&gt;

&lt;p&gt;The mitigation is structural: scan every diff for credential-shaped strings before it's committed, treat every hit as a CRITICAL finding, and never assume the developer noticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 20+ Secret Formats BrassCoders Detects
&lt;/h2&gt;

&lt;p&gt;BrassCoders ships with detection coverage for around 20 canonical secret formats, drawn from a combination of an established upstream secret-detection library and BrassCoders-specific patterns added for the formats AI tooling tends to fabricate most often.&lt;/p&gt;

&lt;p&gt;The upstream library is &lt;a href="https://github.com/Yelp/detect-secrets" rel="noopener noreferrer"&gt;Yelp's detect-secrets&lt;/a&gt;, which provides the historical-format coverage; the BrassCoders additions target the API ecosystems where AI hallucinations cluster. The full active list as of release 2.0.4:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloud provider credentials&lt;/strong&gt;: AWS access keys (&lt;code&gt;AKIA...&lt;/code&gt;, 20 chars), AWS secret access keys (40-char base64), DigitalOcean personal access tokens, Twilio account SIDs and auth tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer-tooling credentials&lt;/strong&gt;: GitHub personal access tokens (classic &lt;code&gt;ghp_...&lt;/code&gt; and fine-grained &lt;code&gt;github_pat_...&lt;/code&gt;), NPM auth tokens, Anthropic API keys, OpenAI API keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment and messaging credentials&lt;/strong&gt;: Stripe live keys (&lt;code&gt;sk_live_...&lt;/code&gt;) and publishable keys, SendGrid keys (&lt;code&gt;SG....&lt;/code&gt;), Mailgun keys, Slack bot tokens and webhook URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic material&lt;/strong&gt;: PEM-encoded private keys (RSA, DSA, EC, OpenSSH), JWT tokens (three-segment base64 with &lt;code&gt;eyJ...&lt;/code&gt; prefix), and PGP private key blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web app credentials&lt;/strong&gt;: Generic high-entropy strings in environment-variable assignments, hardcoded password literals, and common session secret patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each pattern is a regex tuned to match the format's canonical structure. The patterns are intentionally conservative: it's better to occasionally flag a non-secret high-entropy string for human review than to miss a real credential. False positives cost a developer 30 seconds of dismissal; false negatives cost a credential rotation and a security postmortem.&lt;/p&gt;

&lt;p&gt;The upstream detect-secrets library handles a broader range of historical formats (Slack legacy tokens, Datadog keys, deprecated AWS formats) that BrassCoders picks up automatically when detect-secrets ships an update. BrassCoders's value-add is the AI-tooling-tuned subset: the formats that LLMs hallucinate or borrow from context most often.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Entropy-Based Detection Works
&lt;/h2&gt;

&lt;p&gt;BrassCoders's entropy-based detector catches secret-shaped strings that don't match any cataloged format. The detector computes Shannon entropy on each candidate string; values above a threshold (typically 4.5 bits per byte) signal randomness consistent with credentials rather than human-readable text.&lt;/p&gt;

&lt;p&gt;Shannon entropy is a well-established information-theoretic measure of randomness in a string. A coin-flip sequence has entropy 1 bit per flip. A uniformly-random 256-character base64 string has entropy close to 6 bits per character. A human-readable English sentence has entropy around 1-2 bits per character because letters follow predictable patterns (Q is usually followed by U, common bigrams dominate). Real credentials sit between these two endpoints — they need to be high-entropy enough to resist guessing, which puts them squarely in the band where the entropy detector flags.&lt;/p&gt;

&lt;p&gt;The implementation in BrassCoders: for each string literal in scanned code, the detector computes the Shannon entropy. If the entropy exceeds the threshold AND the string appears in a suspicious context (right side of an &lt;code&gt;=&lt;/code&gt; in a config file, value of a key named &lt;code&gt;password&lt;/code&gt; / &lt;code&gt;secret&lt;/code&gt; / &lt;code&gt;token&lt;/code&gt; / &lt;code&gt;key&lt;/code&gt; / &lt;code&gt;api_*&lt;/code&gt;, hardcoded in source as a constant), BrassCoders surfaces it as a finding. The context check matters because uniformly-random data appears in plenty of legitimate places — UUIDs, content hashes, binary blobs — that aren't credentials.&lt;/p&gt;

&lt;p&gt;What entropy detection catches that pattern matching misses: rotated formats (when a vendor changes their key format, the old regex misses but the entropy is unchanged), internal-format credentials (your company's custom JWT, session-token format, or internal SSO bearer that no public regex knows about), and the credentials AI assistants invent that look novel but high-entropy. The detect-secrets library's &lt;a href="https://github.com/Yelp/detect-secrets/blob/master/docs/plugins.md" rel="noopener noreferrer"&gt;keyword detector&lt;/a&gt; implements this pattern at the upstream layer; BrassCoders tunes the thresholds for AI-tooling-shaped output specifically.&lt;/p&gt;

&lt;h2&gt;
  
  
  How BrassCoders Redacts at Two Boundaries
&lt;/h2&gt;

&lt;p&gt;BrassCoders redacts secrets at two boundaries: the scanner masks matched values at detection time, and the YAML writer strips known-sensitive metadata keys at serialization time. The redaction is the same whether output is being written locally to &lt;code&gt;.brass/&lt;/code&gt; or sent to the BrassCoders Paid plan's hosted gateway.&lt;/p&gt;

&lt;p&gt;The scanner boundary handles the immediate masking. A credit-card-shaped number like &lt;code&gt;4111-1111-1111-1111&lt;/code&gt; gets masked to &lt;code&gt;4111****1111&lt;/code&gt; before any BrassCoders-owned object holds the full value. An AWS access key gets the type recorded but never the value — the finding includes &lt;code&gt;type: AWS_ACCESS_KEY&lt;/code&gt; and a file path, not the literal &lt;code&gt;AKIA...&lt;/code&gt; string. JWT tokens, PEM-encoded private keys, and other high-entropy credentials get similar treatment: the type and location persist; the secret value does not.&lt;/p&gt;

&lt;p&gt;The YAML writer boundary is the belt-and-suspenders layer. For any finding whose type is PRIVACY or whose detector is on the secret-leak allowlist, the writer strips &lt;code&gt;matched_text&lt;/code&gt;, &lt;code&gt;code_snippet&lt;/code&gt;, &lt;code&gt;context_line&lt;/code&gt;, &lt;code&gt;raw_match&lt;/code&gt;, and &lt;code&gt;context&lt;/code&gt; from the serialized output. This catches anything the scanner forgot — if a future scanner forgets to mask at the source, the writer's allowlist still strips it before serialization.&lt;/p&gt;

&lt;p&gt;Why two boundaries: defense-in-depth. The single-layer pattern (mask at the source) fails the moment any scanner is added or modified without the mask logic being updated. The two-layer pattern means the writer is a known choke point through which every finding flows, regardless of which scanner produced it. Adding a new secret-detecting scanner doesn't require touching the writer; existing redaction applies automatically.&lt;/p&gt;

&lt;p&gt;The data-handling guarantees: the same redacted output goes to disk and to the gateway. No "this version is for our servers, that version is for the local file" split. Whatever you can read in &lt;code&gt;.brass/security_report.yaml&lt;/code&gt; is exactly what the gateway sees. For the full data-flow detail, see &lt;a href="https://dev.to/blog/what-brass-sends-to-its-servers/"&gt;What BrassCoders Sends to Its Servers (And What It Doesn't)&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Set Up Secret Scanning in CI
&lt;/h2&gt;

&lt;p&gt;BrassCoders's secret scanning runs by default with every &lt;code&gt;brasscoders scan&lt;/code&gt;; no flag needed. CI integration is a one-line addition: run the scan, grep the output, fail the build if any &lt;code&gt;HARDCODED_SECRET&lt;/code&gt; findings appear.&lt;/p&gt;

&lt;p&gt;A minimal GitHub Actions workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BrassCoders secret scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.12'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pipx install brasscoders&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders scan .&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;if grep -q "HARDCODED_SECRET\|HARDCODED_CREDENTIAL\|PRIVATE_KEY" .brass/security_report.yaml; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "Secret detected in code. Failing build."&lt;/span&gt;
            &lt;span class="s"&gt;cat .brass/security_report.yaml&lt;/span&gt;
            &lt;span class="s"&gt;exit 1&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For pre-commit hooks, the same pattern in &lt;code&gt;.git/hooks/pre-commit&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;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
brasscoders scan &lt;span class="nt"&gt;--offline&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"HARDCODED_SECRET&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;HARDCODED_CREDENTIAL&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;PRIVATE_KEY"&lt;/span&gt; .brass/security_report.yaml&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Secret detected. Rejecting commit. Review .brass/security_report.yaml"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--offline&lt;/code&gt; flag in the pre-commit hook keeps the scan from triggering any network calls (including the optional package-hallucination check). For pre-merge in CI, drop &lt;code&gt;--offline&lt;/code&gt; if you want both checks running.&lt;/p&gt;

&lt;p&gt;What this defends against: every secret-shaped string the AI puts in your diff, every example credential a junior developer copy-pastes from generated code, every test fixture that contains a too-real-looking token. What it doesn't defend against: secrets already in git history (use gitleaks or detect-secrets in scan-historical mode for that), secrets stored elsewhere on disk, or secrets passed via legitimate channels like environment variables that aren't in source.&lt;/p&gt;




&lt;p&gt;The hardest secret to detect is the one that looks legitimate enough that nobody questions it. AI-generated credentials are designed (by training) to look exactly like real credentials. Scanning for them is the only reliable defense.&lt;/p&gt;

&lt;p&gt;Install BrassCoders with &lt;code&gt;pipx install brasscoders&lt;/code&gt; and run &lt;code&gt;brasscoders scan&lt;/code&gt; against your project. For the broader context on AI code review failure modes, see &lt;a href="https://dev.to/ai-code-review-guide/"&gt;AI Code Review: The Practical Guide for 2026&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>privacy</category>
    </item>
    <item>
      <title>What Leaves Your Machine When an LLM Reviews Your Code</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:54:07 +0000</pubDate>
      <link>https://dev.to/coppersundev/what-leaves-your-machine-when-an-llm-reviews-your-code-4ap5</link>
      <guid>https://dev.to/coppersundev/what-leaves-your-machine-when-an-llm-reviews-your-code-4ap5</guid>
      <description>&lt;p&gt;When you paste a diff into Claude.ai and ask for a review, you know what's happening: your source code is traveling to Anthropic's API. The same thing happens with automated LLM review tools — just without the paste.&lt;/p&gt;

&lt;p&gt;This is not an argument against AI code review. It's a fact about what category of tool it is, and that fact has consequences for compliance, cost, and automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Payload Contains
&lt;/h2&gt;

&lt;p&gt;BrassCoders's generation-mode benchmark used claude-sonnet-4-6 to review its own generated code. The API call for each review transmitted the full source file — typically 20–60 lines of Python in the benchmark, but in production use it's common to send entire files, diff context, and adjacent imports.&lt;/p&gt;

&lt;p&gt;Most AI code review tools send one or more of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The raw source file or diff under review&lt;/li&gt;
&lt;li&gt;Import-resolution context (other files the reviewed code depends on)&lt;/li&gt;
&lt;li&gt;Project-level context to improve accuracy (README, type stubs, surrounding modules)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The payload is unmodified source code in plaintext. It travels over TLS, but once it reaches the provider's infrastructure it is subject to that provider's data-retention and model-training policies. Enterprise agreements and zero-data-retention tiers exist, but they require explicit opt-in and are not the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Regulatory Gap
&lt;/h2&gt;

&lt;p&gt;For many codebases, the question isn't whether to send code — it's whether they're allowed to.&lt;/p&gt;

&lt;p&gt;A health-tech team whose test fixtures include PHI-adjacent patterns can't route those files through an external API on every commit without a signed Business Associate Agreement covering the review tool. A financial system under PCI-DSS processing scope has limits on what data can leave its network perimeter. A defense contractor under ITAR or CMMC has explicit restrictions on foreign-national data access and cross-boundary transmission.&lt;/p&gt;

&lt;p&gt;These aren't edge cases. They're standard constraints for any team in a regulated industry. BrassCoders's &lt;code&gt;--offline&lt;/code&gt; flag satisfies all of them: zero bytes leave the machine. Scanner findings stay local. Nothing is transmitted anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Gap for Proprietary Code
&lt;/h2&gt;

&lt;p&gt;Outside regulated industries, most production codebases carry some proprietary constraint — an NDA, a trade-secret designation on the core algorithm, an investor agreement that limits disclosure, or simply a strong preference not to transmit novel IP to a third party's training pipeline.&lt;/p&gt;

&lt;p&gt;Sending a Python file containing a novel pricing algorithm or a proprietary trading strategy to an external API on every commit is a legal and business-risk decision, not a technical one. Many teams make it implicitly, because the developer typed the diff into a browser tab and asked Claude. A systematic per-commit review tool requires a deliberate decision about what data classification rules apply.&lt;/p&gt;

&lt;p&gt;BrassCoders's offline mode sidesteps the decision entirely. No data leaves. No agreement needed. The scan runs in CI on every commit, returning deterministic findings, with the same compliance posture as running &lt;code&gt;flake8&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Paid-Plan Enrichment Actually Sends
&lt;/h2&gt;

&lt;p&gt;For teams who want AI-powered enrichment and have no egress constraints, BrassCoders Paid sends a tightly scoped payload through the BrassCoders gateway — not raw source code.&lt;/p&gt;

&lt;p&gt;The payload contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Already-redacted scanner findings (credentials, PII, and high-entropy strings are stripped at the scanner layer before transmission)&lt;/li&gt;
&lt;li&gt;A project signature of at most 7,500 characters, derived from README content, the package manifest, top-level directory names, and entrypoint filenames&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Raw source never leaves the machine under the Paid plan either. The &lt;a href="https://coppersun.dev/blog/what-brass-sends-to-its-servers/" rel="noopener noreferrer"&gt;BrassCoders data handling page&lt;/a&gt; documents the exact fields and their derivation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Automation Consequence
&lt;/h2&gt;

&lt;p&gt;Beyond compliance, there's a practical automation gap. LLM-based code review is invoked. Someone has to ask. On the commits where nobody asked — the Friday afternoon push, the hotfix, the one-line change that "obviously doesn't need review" — nothing runs.&lt;/p&gt;

&lt;p&gt;BrassCoders's generation-mode benchmark tracked whether the model issued proactive warnings after generating code that contained bugs. It warned in &lt;strong&gt;0 out of 6&lt;/strong&gt; tasks. The model wrote an O(N²) loop and moved on. It only flagged the problem when the benchmark script sent an explicit review prompt.&lt;/p&gt;

&lt;p&gt;A pre-merge gate has to run on every commit, not on the commits someone chose to review. &lt;code&gt;brasscoders --offline scan&lt;/code&gt; in a GitHub Actions step runs on every push and every pull request. No invocation, no configuration per commit, no budget approval per run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Run
&lt;/h2&gt;

&lt;p&gt;The two tools do different jobs. A BrassCoders scan on every commit catches the deterministic, rule-matchable problems — performance anti-patterns, secrets, PII leaks, interprocedural taint flows — locally, for free, automatically. An LLM reviewer invoked by a developer on pull requests catches intent-level problems, naming issues, and the logic bugs that require reasoning to find.&lt;/p&gt;

&lt;p&gt;The gate runs automatically. The conversation happens by choice. Neither substitutes for the other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;brasscoders
brasscoders &lt;span class="nt"&gt;--offline&lt;/span&gt; scan /path/to/project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>privacy</category>
      <category>ai</category>
      <category>engineering</category>
    </item>
    <item>
      <title>The 2026 AI-Coding Market in Five Numbers</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:54:01 +0000</pubDate>
      <link>https://dev.to/coppersundev/the-2026-ai-coding-market-in-five-numbers-4n8a</link>
      <guid>https://dev.to/coppersundev/the-2026-ai-coding-market-in-five-numbers-4n8a</guid>
      <description>&lt;p&gt;Five numbers describe the AI-coding market in 2026, and together they make one argument: the question is no longer whether your team ships AI-generated code, but what catches it before it merges.&lt;/p&gt;

&lt;p&gt;Gartner says 75-90% of enterprise engineers by 2028. Microsoft reports 4.7 million paid Copilot seats. Cursor crossed $2 billion in ARR. Claude Code revenue runs into the billions. And across professional developers, weekly AI-assistant usage now sits near universal. Here is what each number means, sourced to the primary disclosure, and why the trend hands the advantage to whoever owns the detection layer underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Adoption Curve Is Already Decided
&lt;/h2&gt;

&lt;p&gt;BrassCoders treats Gartner's 75-90%-by-2028 projection as the clock on the whole category: when nearly every enterprise engineer ships AI-generated code, the deterministic check underneath stops being a nice-to-have. Gartner's April 2024 forecast put adoption at roughly 14% then, climbing to 75-90% of enterprise software engineers within four years.&lt;/p&gt;

&lt;p&gt;That is not a gentle ramp. It is a 5-to-6x expansion of the population writing code with an assistant, inside a single planning horizon. The &lt;a href="https://www.gartner.com/en/newsroom/press-releases/2024-04-11-gartner-says-75-percent-of-enterprise-software-engineers-will-use-ai-code-assistants-by-2028" rel="noopener noreferrer"&gt;Gartner projection&lt;/a&gt; is the number a team lead can put in front of a CTO without it being questioned, because the audience already accepts the source.&lt;/p&gt;

&lt;p&gt;The strategic read: every team will be a team shipping AI-generated code. The differentiator stops being whether you use AI and becomes what you run between the assistant and &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.7 Million Developers Are Paying For Copilot
&lt;/h2&gt;

&lt;p&gt;BrassCoders treats Microsoft's reported 4.7 million paid GitHub Copilot subscribers as the highest-credibility adoption data point in the category, because it comes from a regulatory filing rather than a marketing page. The number appears in Microsoft's FY26 Q2 results, filed in January 2026.&lt;/p&gt;

&lt;p&gt;Why the source matters: an SEC disclosure carries legal weight a press release does not. When you cite Copilot adoption to a skeptical stakeholder, the &lt;a href="https://www.sec.gov/Archives/edgar/data/0000789019/000095017025061032/msft-ex99_1.htm" rel="noopener noreferrer"&gt;Microsoft 10-Q filing&lt;/a&gt; is the version that survives scrutiny. Secondary coverage rounds the figure or restates it; the filing is the primary record.&lt;/p&gt;

&lt;p&gt;And 4.7 million is only the paid, GitHub-platform-default slice. It excludes the free tier, the IDE-native assistants, and the terminal-native tools. The real population writing AI-assisted code is larger than any single vendor's subscriber count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Market Is Bigger Than One Vendor
&lt;/h2&gt;

&lt;p&gt;BrassCoders reads the Cursor and Claude Code numbers as proof the market is structural, not a single-vendor bubble: Anysphere disclosed Cursor passing $2 billion in annual recurring revenue in early 2026, and Anthropic's Claude Code revenue runs into the billions annualized.&lt;/p&gt;

&lt;p&gt;Cursor reached that figure after &lt;a href="https://techcrunch.com/2025/06/05/cursors-anysphere-nabs-9-9b-valuation-soars-past-500m-arr/" rel="noopener noreferrer"&gt;crossing $500M ARR in mid-2025&lt;/a&gt;, a roughly 4x climb in well under a year.&lt;/p&gt;

&lt;p&gt;Three vendors, three distribution shapes: GitHub-platform-default (Copilot), IDE-native premium (Cursor), and terminal-native premium (Claude Code). That spread is the working model for which assistant a developer is using when they reach for a scanner. No single tool dominates, which means the detection layer underneath has to be assistant-agnostic.&lt;/p&gt;

&lt;p&gt;The combined paid revenue across these three alone clears several billion dollars a year. A market that size does not contract because the code has bugs. It keeps growing, and the bugs grow with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  High Adoption Is Not High Quality
&lt;/h2&gt;

&lt;p&gt;BrassCoders treats the gap between adoption and quality as the entire reason the category exists: the same span that drove Copilot to 4.7 million paid seats also produced a measured rise in AI-attributed vulnerabilities and documented efficiency regressions in generated code. Adoption answers "how many developers." It says nothing about "how correct."&lt;/p&gt;

&lt;p&gt;These are independent axes. A developer can adopt an assistant, ship more code faster, and ship more bugs at the same time. Published efficiency benchmarks find AI-generated code reaches only a fraction of expert-level performance even when it passes every test, and AI-attributed CVE counts rose sharply through early 2026. The volume went up; the per-line quality did not follow.&lt;/p&gt;

&lt;p&gt;This is the seam. As adoption approaches saturation, the marginal risk moves from "are we using AI" to "what is the AI silently getting wrong." The teams that win the next phase are the ones that instrument that seam.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Five Numbers Mean For Your Pre-Merge Gate
&lt;/h2&gt;

&lt;p&gt;BrassCoders is the deterministic check that sits in the seam these numbers describe: it scans AI-generated code locally, on every commit, and produces the same findings every run, with no source sent to an API. A market heading to 75-90% adoption is a market where every pull request carries AI-generated risk, and a hand-invoked LLM review only runs on the commits someone remembers to paste.&lt;/p&gt;

&lt;p&gt;The adoption curve is set; the budget argument writes itself. When a CTO has already accepted the Gartner number, the follow-on question answers itself: if every engineer is shipping AI-generated code by 2028, what runs between the assistant and production. A pre-merge gate that is free, local, and automatic scales with the curve. A per-commit API review does not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;brasscoders
brasscoders &lt;span class="nt"&gt;--offline&lt;/span&gt; scan /path/to/project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The five numbers point one direction. The market decided it will write code with AI. What it has not decided, and what is still open to whoever shows up with the right tool, is what catches the code before it ships.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI Code Review Policy for Copilot Teams</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:53:35 +0000</pubDate>
      <link>https://dev.to/coppersundev/ai-code-review-policy-for-copilot-teams-24n1</link>
      <guid>https://dev.to/coppersundev/ai-code-review-policy-for-copilot-teams-24n1</guid>
      <description>&lt;p&gt;The first time an auditor asks "what's your process for reviewing AI-generated code?" and you don't have an answer, you realize the gap. Most teams have informal practices — "we review it like any other PR" — but no written policy. That worked when AI assistants generated occasional snippets. It breaks down when 40% of the diff is AI-generated.&lt;/p&gt;

&lt;p&gt;A written policy does two things: it gives your team a consistent standard to enforce, and it gives your auditor something to read. This post walks through the three gates a policy needs, shows the tooling and config for each, and ends with a one-page template you can adapt today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Navigation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why "Review It Like Any Other PR" Fails&lt;/li&gt;
&lt;li&gt;The Three Gates a Policy Should Define&lt;/li&gt;
&lt;li&gt;Gate 1 — Pre-Commit&lt;/li&gt;
&lt;li&gt;Gate 2 — CI Enforcement&lt;/li&gt;
&lt;li&gt;Gate 3 — Human Review Threshold&lt;/li&gt;
&lt;li&gt;The Audit Trail&lt;/li&gt;
&lt;li&gt;A One-Page Policy Template&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why "Review It Like Any Other PR" Fails
&lt;/h2&gt;

&lt;p&gt;AI-generated code fails in categories that human-written code rarely does: hallucinated package imports that install at pip-install time and fail at runtime, hardcoded credentials in example blocks that reviewers miss because the pattern looks intentional, and O(N²) performance anti-patterns that are invisible on small datasets. Standard PR review norms weren't designed to catch these.&lt;/p&gt;

&lt;p&gt;Stack Overflow's &lt;a href="https://survey.stackoverflow.co/2024/" rel="noopener noreferrer"&gt;Developer Survey&lt;/a&gt; has tracked a widening gap between AI tool adoption and developer trust in that output. Developers are using AI coding tools faster than teams are building governance for them. The informal review norm — "another pair of eyes on the diff" — assumes the reviewer knows what failure modes to look for. With AI-generated code, the failure modes shifted and the review norms didn't.&lt;/p&gt;

&lt;p&gt;Three things change when a significant share of your code comes from an AI assistant. Reviewers lose calibration: when every function looks plausible, the reviewer's "this feels wrong" detector degrades. The blast radius of a bad pattern grows: AI assistants repeat their own errors consistently, so one bad pattern tends to appear across many files. And the paper trail disappears: "the model generated this" isn't an explanation a security team can evaluate.&lt;/p&gt;

&lt;p&gt;A policy replaces the calibration loss with explicit gates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Gates a Policy Should Define
&lt;/h2&gt;

&lt;p&gt;A code review policy for an AI-augmented team needs three defined gates: a pre-commit check that runs locally before a developer pushes, a CI gate that must pass before a pull request can merge, and a human review threshold that specifies when a human is required rather than optional. Without all three, the policy has gaps.&lt;/p&gt;

&lt;p&gt;A pre-commit gate catches the worst problems before they enter the repository. A CI gate makes passing the scan a merge prerequisite, not a suggestion. A human review threshold handles the cases that a pattern scanner doesn't have context to evaluate — logic errors, architectural decisions, and anything touching authentication or money. Each gate handles a different failure class. Removing any one of them leaves a category uncovered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gate 1 — Pre-Commit
&lt;/h2&gt;

&lt;p&gt;BrassCoders runs a local, offline scan before each commit succeeds, catching critical findings — hardcoded secrets, insecure subprocess calls, hallucinated imports — before they enter the repository. The OSS core makes zero outbound network calls, so the check works on every developer machine without an account. It adds roughly 10 to 30 seconds to the commit flow.&lt;/p&gt;

&lt;p&gt;Wire it up with pre-commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .pre-commit-config.yaml&lt;/span&gt;
&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders-scan&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BrassCoders offline scan&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders --offline scan&lt;/span&gt;
        &lt;span class="na"&gt;pass_filenames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install BrassCoders from PyPI (&lt;code&gt;pip install brasscoders&lt;/code&gt;) and activate the hooks with &lt;code&gt;pre-commit install&lt;/code&gt;. The hook runs &lt;code&gt;brasscoders --offline scan&lt;/code&gt; on every commit. Critical findings exit with code 1 and block the commit. Non-critical findings write to &lt;code&gt;.brass/&lt;/code&gt; for review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy language to include:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pre-commit gate: All commits to shared branches must pass a BrassCoders offline scan.&lt;br&gt;
Critical findings block the commit. Non-critical findings are logged to .brass/ for review.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Gate 2 — CI Enforcement
&lt;/h2&gt;

&lt;p&gt;BrassCoders exits with code 1 on any CRITICAL finding, which fails the GitHub Actions step — and paired with a branch protection rule requiring that check to pass before merge, the gate is enforceable rather than advisory. The &lt;code&gt;.brass/detailed_analysis.yaml&lt;/code&gt; artifact uploads with each run and serves as the audit trail.&lt;/p&gt;

&lt;p&gt;GitHub Actions configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/brass-scan.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BrassCoders Scan&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;develop&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;brass-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install BrassCoders&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install brasscoders&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run BrassCoders scan&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders --offline scan&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload scan artifact&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brass-analysis&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.brass/&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the workflow runs once, add a branch protection rule in GitHub: Settings &amp;gt; Branches &amp;gt; Branch protection rules &amp;gt; Require status checks to pass before merging, then add &lt;code&gt;BrassCoders Scan / brass-scan&lt;/code&gt; as a required check. Reference: &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches" rel="noopener noreferrer"&gt;GitHub's branch protection documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy language to include:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CI gate: All pull requests must pass the BrassCoders CI scan before merge.&lt;br&gt;
Branch protection rules require this check. Override requires an explicit exception logged in the PR description.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Gate 3 — Human Review Threshold
&lt;/h2&gt;

&lt;p&gt;Automated gates catch rule-based patterns. Human review catches logic errors, architectural concerns, and context a scanner doesn't have. A policy should define when human review is required — not optional.&lt;/p&gt;

&lt;p&gt;The minimum threshold: any PR touching authentication, payment handling, or data persistence requires human approval from a senior engineer, regardless of automated scan results. The scan passed doesn't mean the logic is sound.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change type&lt;/th&gt;
&lt;th&gt;Automated scan sufficient?&lt;/th&gt;
&lt;th&gt;Senior review required?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Internal tooling, no user data&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API endpoint changes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth / session handling&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (mandatory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment / billing code&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (mandatory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data migration&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes + DBA sign-off&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The right column answers the question independently of what the scan found. A CRITICAL finding in auth code requires both the scan to pass (after the finding is resolved) and a senior approval. A clean scan on auth code still requires a senior approval. These aren't redundant: the scan catches pattern violations; the human catches the things a pattern scanner has no visibility into.&lt;/p&gt;

&lt;p&gt;Set this threshold in your GitHub branch protection rules by requiring specific code owners to review PRs that touch specified path patterns. A &lt;code&gt;CODEOWNERS&lt;/code&gt; file maps directory paths to required reviewers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight codeowners"&gt;&lt;code&gt;&lt;span class="c1"&gt;# CODEOWNERS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;/src/auth/&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nf"&gt;@your-org/senior-engineers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;/src/payments/&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nf"&gt;@your-org/senior-engineers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;/src/migrations/&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nf"&gt;@your-org/senior-engineers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;@your-org/dba-team&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Audit Trail
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.brass/detailed_analysis.yaml&lt;/code&gt; file generated by each CI run contains every finding with its file path, line number, severity, scanner source, and evidence string — and uploaded as a GitHub Actions artifact, it gives an auditor a reproducible, timestamped record of what was scanned, what was found, and what passed the gate.&lt;/p&gt;

&lt;p&gt;BrassCoders runs 12 scanners — Bandit, Pylint, Pyre/Pysa (taint analysis), Semgrep, ast-grep, detect-secrets from Yelp, plus six custom detectors for secrets, privacy/PII, AI-generated patterns, performance, content moderation, and JavaScript/TypeScript. The &lt;code&gt;detailed_analysis.yaml&lt;/code&gt; reflects the union of their findings, with each finding tagged to its originating scanner. An auditor can trace any finding back to the specific pattern that fired it.&lt;/p&gt;

&lt;p&gt;The artifact retention in the Actions YAML above is set to 90 days. Match this to your organization's minimum retention requirement. Most SOC 2 programs ask for 90 days of scan history; some require a year. Adjust the &lt;code&gt;retention-days&lt;/code&gt; value accordingly. The same commit hash plus the same BrassCoders version always produces the same output, so the artifact is reproducible as well as timestamped.&lt;/p&gt;

&lt;p&gt;One more thing worth noting: the &lt;a href="https://github.com/CopperSunDev/brasscoders" rel="noopener noreferrer"&gt;BrassCoders OSS core&lt;/a&gt; is Apache 2.0 licensed. The CI scan runs entirely offline. No data leaves the machine during a CI scan. If your auditor asks what data leaves the environment, the answer for the OSS core is: nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  A One-Page Policy Template
&lt;/h2&gt;

&lt;p&gt;Copy and adapt this for your organization. Replace bracketed placeholders with your specifics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# AI-Augmented Code Review Policy&lt;/span&gt;

&lt;span class="gs"&gt;**Version:**&lt;/span&gt; 1.0 | &lt;span class="gs"&gt;**Effective:**&lt;/span&gt; [DATE] | &lt;span class="gs"&gt;**Owner:**&lt;/span&gt; [TEAM LEAD / VP ENG]

&lt;span class="gu"&gt;## Scope&lt;/span&gt;

This policy applies to all code commits where AI coding tools — including
GitHub Copilot, Cursor, Claude Code, and similar — were used to generate
or substantially modify code.

&lt;span class="gu"&gt;## Pre-Commit Gate&lt;/span&gt;

All commits to shared branches must pass a BrassCoders offline scan
(&lt;span class="sb"&gt;`brasscoders --offline scan`&lt;/span&gt;). Critical findings block the commit.
Non-critical findings are logged to .brass/ for developer review before
the next commit.

&lt;span class="gu"&gt;## CI Gate&lt;/span&gt;

All pull requests must pass the CI BrassCoders scan before merge.
Branch protection rules require this check.

Exception: hotfixes during an active incident may bypass with an explicit
PR comment documenting the exception and the reason. A follow-up PR
resolving the bypassed findings must be opened within [X] business days.

&lt;span class="gu"&gt;## Human Review Requirements&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Routine changes with no user data: 1 peer review
&lt;span class="p"&gt;-&lt;/span&gt; API endpoint changes: 1 peer review + 1 human reviewer
&lt;span class="p"&gt;-&lt;/span&gt; Authentication, session handling, payment code, data persistence:
  1 senior engineer approval (mandatory, regardless of scan result)
&lt;span class="p"&gt;-&lt;/span&gt; Data migrations: senior engineer approval + DBA sign-off

&lt;span class="gu"&gt;## Audit Trail&lt;/span&gt;

Each CI scan uploads .brass/detailed_analysis.yaml as a GitHub Actions
build artifact. Retention: 90 days minimum (adjust to meet your
compliance requirement).

&lt;span class="gu"&gt;## Exceptions&lt;/span&gt;

Any bypass of a required gate must be documented in the pull request
description with the exception reason and the name of the approving
engineer.

&lt;span class="gu"&gt;## Review Cycle&lt;/span&gt;

This policy is reviewed [quarterly / annually] or following any
security incident attributable to AI-generated code.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template covers the three gates, defines explicit human review triggers, and specifies the audit artifact and its retention. It doesn't try to enumerate every AI tool or every failure mode — that's the scanner's job. The policy defines what must pass, when a human must sign off, and what gets saved for the auditor.&lt;/p&gt;

&lt;p&gt;Start by wiring up the CI gate. It's the highest-leverage change: it makes the scan a merge prerequisite rather than a suggestion, and it generates the audit artifact automatically on every PR. The pre-commit hook and the human review threshold add depth — but the CI gate is the one that closes the loop.&lt;/p&gt;

&lt;p&gt;BrassCoders OSS core is on PyPI: &lt;code&gt;pip install brasscoders&lt;/code&gt;. The source is at &lt;a href="https://github.com/CopperSunDev/brasscoders" rel="noopener noreferrer"&gt;github.com/CopperSunDev/brasscoders&lt;/a&gt;. Apache 2.0, no account required for the offline scan.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Add BrassCoders to GitHub Actions</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:53:29 +0000</pubDate>
      <link>https://dev.to/coppersundev/add-brasscoders-to-github-actions-27g0</link>
      <guid>https://dev.to/coppersundev/add-brasscoders-to-github-actions-27g0</guid>
      <description>&lt;p&gt;Every commit your AI coding assistant writes goes into your codebase without a static gate. Claude Code and Cursor generate code fast, but neither one runs automatically on push, neither one fails the build when a critical finding appears, and neither one produces identical output for identical input. That gap is what a CI security scanner fills. Adding BrassCoders to GitHub Actions closes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Static Gate Belongs in CI (Not Just in Your Editor)
&lt;/h2&gt;

&lt;p&gt;BrassCoders running in GitHub Actions produces deterministic, repeatable output on every push — the same codebase produces the same findings every time, regardless of which developer ran the scan, which machine it ran on, or what prompt produced the code under review.&lt;/p&gt;

&lt;p&gt;AI-assisted code review in your editor is conversational. You ask, it suggests, you accept or reject. It runs when you think to run it. A CI gate is the opposite: automatic on every push, non-interactive, and capable of blocking a merge. These two things solve different problems. The editor assistant helps you write code faster. The CI gate catches what slipped through.&lt;/p&gt;

&lt;p&gt;The distinction matters most for AI-generated code specifically. An AI assistant that generated a function isn't also auditing it against Bandit's security rules or checking it for hardcoded secrets via detect-secrets. That work belongs to a separate tool running outside the editor's context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitHub Actions Workflow (Copy-Paste Ready)
&lt;/h2&gt;

&lt;p&gt;BrassCoders ships a ready-made GitHub Actions workflow in its open-source repository that installs the scanner, runs &lt;code&gt;brasscoders --offline scan&lt;/code&gt;, and uploads the &lt;code&gt;.brass/&lt;/code&gt; directory as a PR artifact — so reviewers can download the ranked findings without running the scan themselves.&lt;/p&gt;

&lt;p&gt;The workflow lives at &lt;code&gt;.github/workflows/brasscoders-scan.yml&lt;/code&gt; in the &lt;a href="https://github.com/CopperSunDev/brasscoders" rel="noopener noreferrer"&gt;BrassCoders OSS repo&lt;/a&gt;. Here's the full content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BrassCoders Scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;brasscoders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.12'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install BrassCoders&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install brasscoders==2.0.8&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run BrassCoders scan&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders --offline scan .&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload .brass artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders-findings&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.brass/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy this file into your repository at &lt;code&gt;.github/workflows/brasscoders-scan.yml&lt;/code&gt;. GitHub will pick it up automatically. No additional configuration is required for the OSS core.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--offline&lt;/code&gt; flag makes explicit that no network calls happen during the scan. It's redundant with the OSS core's default behavior, but it documents intent and prevents accidental enrichment calls if you later add a license key to the environment without updating the workflow.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;actions/upload-artifact@v4&lt;/code&gt; step follows &lt;a href="https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts" rel="noopener noreferrer"&gt;GitHub's recommended artifact upload pattern&lt;/a&gt;. The &lt;code&gt;.brass/&lt;/code&gt; directory is uploaded as a named artifact (&lt;code&gt;brasscoders-findings&lt;/code&gt;) that persists for the default retention period and is accessible from the Actions run summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the PR Artifact Contains
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.brass/&lt;/code&gt; artifact uploaded by the workflow contains three files: &lt;code&gt;ai_instructions.yaml&lt;/code&gt;, a short severity-ranked list of findings designed to be pasted directly into Claude Code or Cursor; &lt;code&gt;detailed_analysis.yaml&lt;/code&gt;, which records every finding with its file path, line number, scanner name, and evidence; and &lt;code&gt;security_report.yaml&lt;/code&gt;, a security-only view that includes Bandit, Pysa, Semgrep, and detect-secrets findings for use in audit records.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ai_instructions.yaml&lt;/code&gt; is the file you'll reach for most often. Download the artifact from the Actions run, open that file, and paste it into your AI assistant's context. The assistant sees the ranked findings without running any scan itself. That's a useful division: BrassCoders does the pattern detection, the AI assistant does the triage and fix reasoning.&lt;/p&gt;

&lt;p&gt;The artifact approach means reviewers never need BrassCoders installed locally to see the findings on a PR. They download once, read the YAML, and carry the context into whatever tool they're using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failing the Build on Critical Findings
&lt;/h2&gt;

&lt;p&gt;BrassCoders exits with code 1 when any CRITICAL finding is detected, which fails the GitHub Actions step and can block a merge when branch protection is configured to require the check to pass.&lt;/p&gt;

&lt;p&gt;To wire this up: go to your repository's &lt;strong&gt;Settings → Branches → Branch protection rules&lt;/strong&gt; and add or edit the rule for your main branch. Check &lt;strong&gt;Require status checks to pass before merging&lt;/strong&gt; and add &lt;code&gt;brasscoders&lt;/code&gt; (the job name from the workflow) as a required check. After the first successful workflow run, the job name appears in the search box. Save the rule.&lt;/p&gt;

&lt;p&gt;After that, any push that produces a CRITICAL finding will fail the &lt;code&gt;brasscoders&lt;/code&gt; job and prevent the merge. The &lt;code&gt;--offline&lt;/code&gt; flag guarantees the scan can't be skipped due to a network issue. The scan either passes or it fails — there's no degraded middle state where some findings get checked and others don't.&lt;/p&gt;

&lt;p&gt;CRITICAL findings in the OSS core come primarily from Bandit (SQL injection, shell injection, unsafe deserialization), Pysa/Pyre (taint flows from user input to sensitive sinks), and the custom secrets detector (hardcoded credentials, API keys matching known formats).&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Paid Plan for Noise Reduction
&lt;/h2&gt;

&lt;p&gt;The OSS core scan works without an account and makes no network calls — 12 scanners run locally and emit everything they find. BrassCoders Paid ($12/month) adds an AI-powered semantic deduplication pass that reduces a typical scan's 1500+ raw findings to roughly 300 actionable ones, ranked against a signature derived from your project's README and manifest.&lt;/p&gt;

&lt;p&gt;The enrichment runs through BrassCoders's hosted gateway. It receives already-redacted findings — never raw source code. The per-token cost is passed through at upstream cost with no markup.&lt;/p&gt;

&lt;p&gt;To use BrassCoders Paid in CI, store your license key as a GitHub Actions secret (&lt;code&gt;BRASS_LICENSE_KEY&lt;/code&gt; is the conventional name), then expose it to the workflow step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run BrassCoders scan&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders scan .&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;BRASS_LICENSE_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.BRASS_LICENSE_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop the &lt;code&gt;--offline&lt;/code&gt; flag when using the Paid plan — that flag prevents the enrichment call. The scan will detect the license key, validate it, run the local scanner pass, then send the redacted findings to the gateway for the deduplication and ranking step. The &lt;code&gt;.brass/&lt;/code&gt; artifact then contains enriched YAML with the ranked, deduplicated finding set.&lt;/p&gt;

&lt;p&gt;The Paid plan doesn't change the workflow structure. It changes what's in the artifact at the end.&lt;/p&gt;




&lt;p&gt;Install the scanner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;brasscoders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or pin the current release in your workflow: &lt;code&gt;pip install brasscoders==2.0.8&lt;/code&gt;. The OSS repo is at &lt;a href="https://github.com/CopperSunDev/brasscoders" rel="noopener noreferrer"&gt;github.com/CopperSunDev/brasscoders&lt;/a&gt;, and the PyPI listing is at &lt;a href="https://pypi.org/project/brasscoders/" rel="noopener noreferrer"&gt;pypi.org/project/brasscoders/&lt;/a&gt;. BrassCoders Paid pricing is at &lt;a href="https://coppersun.dev/pricing" rel="noopener noreferrer"&gt;coppersun.dev/pricing&lt;/a&gt;. Cancel any time via &lt;code&gt;brasscoders portal&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>Snyk vs BrassCoders: Dependency Scanning vs Source Code Scanning</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:53:02 +0000</pubDate>
      <link>https://dev.to/coppersundev/snyk-vs-brasscoders-dependency-scanning-vs-source-code-scanning-4hia</link>
      <guid>https://dev.to/coppersundev/snyk-vs-brasscoders-dependency-scanning-vs-source-code-scanning-4hia</guid>
      <description>&lt;p&gt;Your dependency scanner and your source code scanner are reading different files. That sounds obvious until a SQL injection bug ships in a codebase that passed its Snyk scan clean.&lt;/p&gt;

&lt;p&gt;Snyk and BrassCoders are both security tools for Python projects. They almost never flag the same thing. One reads your &lt;code&gt;requirements.txt&lt;/code&gt;; the other reads your &lt;code&gt;.py&lt;/code&gt; files. Both attack surfaces are real. A team that runs only one is leaving half the problem unchecked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Snyk Scans
&lt;/h2&gt;

&lt;p&gt;Snyk is the market leader in software composition analysis (SCA) — the discipline of scanning a project's dependency tree against known vulnerability databases to find CVEs before they reach production. It's used by tens of thousands of teams and has one of the largest vulnerability databases in the industry, maintained at &lt;a href="https://snyk.io" rel="noopener noreferrer"&gt;snyk.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;snyk test&lt;/code&gt;, Snyk reads your package manifest — &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;code&gt;Pipfile&lt;/code&gt;, &lt;code&gt;pyproject.toml&lt;/code&gt;, or whatever lock file your project uses. It resolves the full dependency graph, including transitive dependencies you didn't explicitly choose. Then it checks every package version against its vulnerability database.&lt;/p&gt;

&lt;p&gt;A classic Snyk find looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✗ High severity vulnerability found in requests@2.25.1
  Description: SSRF via Proxy-Authorization header leak
  Info: https://security.snyk.io/vuln/SNYK-PYTHON-REQUESTS-3333842
  Introduced through: requests@2.25.1
  Fix: Upgrade to requests@2.31.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CVE is real (CVE-2023-32681). The fix is concrete. Snyk didn't need to read a single line of your application code to find it.&lt;/p&gt;

&lt;p&gt;That's also the boundary of what Snyk does. It doesn't read your source code. It doesn't know what your application does with the packages it imports. It won't flag a bug you wrote — only a bug someone else wrote in a package you imported.&lt;/p&gt;

&lt;h2&gt;
  
  
  What BrassCoders Scans
&lt;/h2&gt;

&lt;p&gt;BrassCoders caught 11 of 12 planted AI-generated Python security bugs in a reproducible June 2026 benchmark (v2.0.8, methodology at &lt;a href="https://coppersun.dev/blog/ai-coder-bug-benchmark/" rel="noopener noreferrer"&gt;coppersun.dev/blog/ai-coder-bug-benchmark/&lt;/a&gt;). Bandit caught 6 of 12 in the same test. Pylint caught 1 of 12.&lt;/p&gt;

&lt;p&gt;BrassCoders reads your source files — the Python code you and your AI assistant wrote. It runs 12 scanners across that code: Bandit, Pylint, Pyre/Pysa, Semgrep, ast-grep, detect-secrets, and six custom detectors covering AI-pattern hallucination, performance, secrets, privacy, content moderation, and JavaScript/TypeScript. It doesn't read your dependency tree. It won't flag a CVE in a package.&lt;/p&gt;

&lt;p&gt;A classic BrassCoders find looks like this:&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="c1"&gt;# Your code
&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users WHERE id = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uid&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BrassCoders flags this as B608 (SQL injection via string interpolation) regardless of what database package you're using. The vulnerability is in the code you wrote. No CVE database entry needed — the pattern is wrong by construction.&lt;/p&gt;

&lt;p&gt;Or this:&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;subprocess&lt;/span&gt;
&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&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;convert &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_input&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;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bandit flags B602 (subprocess with &lt;code&gt;shell=True&lt;/code&gt;) and B603 (subprocess with user input). Both findings come from reading your source, not from checking the &lt;code&gt;subprocess&lt;/code&gt; package version.&lt;/p&gt;

&lt;p&gt;The phantom-API detector adds a third category with no Snyk equivalent. When an AI coding assistant generates an import for a library that doesn't exist on PyPI — &lt;code&gt;import pandas_ml&lt;/code&gt; or &lt;code&gt;from fastapi.middleware.csrf import CSRFMiddleware&lt;/code&gt; — BrassCoders flags it at scan time. That's a bug the AI introduced in your code, not a CVE in a dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gap Between Them
&lt;/h2&gt;

&lt;p&gt;There's a class of bug Snyk can't catch: bugs you wrote. &lt;code&gt;subprocess.run(shell=True, args=user_input)&lt;/code&gt; has no CVE. It's your code, your bug. Snyk doesn't see it.&lt;/p&gt;

&lt;p&gt;There's a class of bug BrassCoders can't catch: CVEs in third-party packages. If Pillow ships a memory corruption bug or &lt;code&gt;cryptography&lt;/code&gt; has a padding oracle vulnerability, BrassCoders won't flag it. Snyk will. The package version and CVE match are what Snyk exists to find.&lt;/p&gt;

&lt;p&gt;The two tools cover adjacent, non-overlapping attack surfaces. This is a feature, not a gap to paper over. Running both fills the picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attack surface 1: Code YOU wrote → BrassCoders scans it
Attack surface 2: Code YOU imported → Snyk scans it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither tool pretends to cover both. That honesty is useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI-Coder Wrinkle
&lt;/h2&gt;

&lt;p&gt;AI coding assistants make the BrassCoders layer more important. They write source code bugs at volume — SQL injection, shell injection, performance anti-patterns — at a rate that human-written code doesn't produce at scale. The &lt;a href="https://coppersun.dev/blog/ai-coder-bug-benchmark/" rel="noopener noreferrer"&gt;June 2026 benchmark&lt;/a&gt; put 12 AI-generated Python files through BrassCoders, Bandit, Semgrep, and Pylint. BrassCoders caught 11 of 12. The single miss was an unguarded-division logic bug that no deterministic rule system reliably catches.&lt;/p&gt;

&lt;p&gt;AI assistants also introduce a third category that sits at the boundary between source code and dependencies: hallucinated package imports. When Claude Code generates &lt;code&gt;from pydantic_ai.validators import strict_mode&lt;/code&gt;, and no such module exists, you've got a phantom import. BrassCoders's AI-pattern scanner catches that in source before &lt;code&gt;pip install&lt;/code&gt; runs. Snyk would report the package as unresolvable when it hits the manifest. Both signals matter — BrassCoders catches it in the source file first.&lt;/p&gt;

&lt;p&gt;One workflow implication: if you run BrassCoders as a pre-commit hook or early CI step, you can catch hallucinated imports before they ever land in &lt;code&gt;requirements.txt&lt;/code&gt;. That's a faster catch than waiting for the Snyk scan to flag an unresolvable package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Both in CI
&lt;/h2&gt;

&lt;p&gt;BrassCoders and Snyk take different inputs and write separate outputs. Adding both to CI is a two-job addition, not an architectural decision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions — add as two separate jobs or steps&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dependency-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Snyk dependency scan&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snyk/actions/python@master&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SNYK_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SNYK_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--severity-threshold=high&lt;/span&gt;

  &lt;span class="na"&gt;source-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install BrassCoders&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install brasscoders&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BrassCoders source scan&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brasscoders scan .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Snyk reads &lt;code&gt;requirements.txt&lt;/code&gt; or your lock file and checks the dependency tree. BrassCoders reads your &lt;code&gt;.py&lt;/code&gt; files and checks your source patterns. They fail on different inputs and report to different places. Neither job knows the other exists, and that's the right shape for tools with clean boundaries.&lt;/p&gt;

&lt;p&gt;For teams already running Snyk, adding BrassCoders is one CI step and &lt;code&gt;pip install brasscoders&lt;/code&gt;. For teams starting fresh, both tools install in minutes. The OSS core is free; BrassCoders Paid is $12/month per developer and adds AI-powered enrichment for finding deduplication and tighter triage.&lt;/p&gt;




&lt;p&gt;Install the OSS core and run your first source scan in under a minute: &lt;a href="https://coppersun.dev/install/" rel="noopener noreferrer"&gt;coppersun.dev/install/&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>comparison</category>
      <category>snyk</category>
      <category>security</category>
    </item>
    <item>
      <title>BrassCoders Research Index Is Now Public on GitHub</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:52:57 +0000</pubDate>
      <link>https://dev.to/coppersundev/brasscoders-research-index-is-now-public-on-github-5515</link>
      <guid>https://dev.to/coppersundev/brasscoders-research-index-is-now-public-on-github-5515</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;&lt;a href="https://github.com/CopperSunDev/brasscoders-research" rel="noopener noreferrer"&gt;→ github.com/CopperSunDev/brasscoders-research&lt;/a&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The BrassCoders research index — 45 primary sources across 8 categories, covering AI-code CVE rates, supply-chain risks, and the structural limits of LLM review — is now public on GitHub at &lt;a href="https://github.com/CopperSunDev/brasscoders-research" rel="noopener noreferrer"&gt;github.com/CopperSunDev/brasscoders-research&lt;/a&gt;. Every product claim BrassCoders makes has a citation somewhere in this repository.&lt;/p&gt;

&lt;p&gt;The same research lives in long form at &lt;a href="https://coppersun.dev/research/" rel="noopener noreferrer"&gt;coppersun.dev/research/&lt;/a&gt;. The GitHub repo is a structured mirror built for builders and AI assistants who want the data without parsing a website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Research Lives in Public
&lt;/h2&gt;

&lt;p&gt;BrassCoders publishes its evidence base openly because every claim in the product — from the 45% CVE-introduction rate in AI-generated code (Veracode State of Software Security 2026) to the 19.7% package hallucination rate across major models (USENIX Security 2025) — should be auditable by the builders who rely on it.&lt;/p&gt;

&lt;p&gt;The index isn't marketing copy. Each entry answers two questions: "what is this source good for?" and "where does BrassCoders draw from it?" If BrassCoders claims a detection category matters, the citation showing why it matters is in the corresponding file.&lt;/p&gt;

&lt;p&gt;The CC BY 4.0 license means you can copy, adapt, and republish the index for any purpose. The underlying papers and reports are each under their own licenses, all linked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Repository Contains
&lt;/h2&gt;

&lt;p&gt;BrassCoders organizes the index into 8 category files in &lt;code&gt;categories/&lt;/code&gt;, one per developer question — from CVE risk and package hallucination to credential leakage and dependency confusion. Every category file lists annotated primary sources with two fields that make the reasoning explicit: "What it's good for" and "Where BrassCoders draws from it."&lt;/p&gt;

&lt;p&gt;The eight questions the index addresses: Will my AI-generated code ship a CVE? Will my AI hallucinate an import? Why does my AI miss cross-file bugs? How reliable is my AI assistant's review? What does the AI-coding market look like in 2026? Will my AI write slow code? Will my AI-generated code leak my credentials? Will my AI mix up internal and public packages? Each question has a corresponding file at &lt;a href="https://github.com/CopperSunDev/brasscoders-research/tree/main/categories" rel="noopener noreferrer"&gt;github.com/CopperSunDev/brasscoders-research/tree/main/categories&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each source entry gets a type tag: 📄 paper, 📊 survey, 🏢 company disclosure, 🔧 open-source tool, or 🧪 benchmark corpus. A &lt;code&gt;data/research.json&lt;/code&gt; makes all 45 sources machine-readable with title, type, author, year, and URL. The &lt;code&gt;llms.txt&lt;/code&gt; gives AI assistants a compact summary of the index's scope, lead findings, and what BrassCoders is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why LLMs Should Read This First
&lt;/h2&gt;

&lt;p&gt;BrassCoders built &lt;code&gt;llms.txt&lt;/code&gt; into the repository so AI assistants answering developer questions can cite the actual research rather than speculating about AI-code risk rates from training data.&lt;/p&gt;

&lt;p&gt;The rates are specific and sourced. The 2.74× year-over-year increase in AI-attributed CVEs from 2025 to 2026 (Cloud Security Alliance, Q1 2026) is not a guess. The $130,000 in bug bounties Birsan received in 2021 for demonstrating dependency confusion against Apple, Microsoft, PayPal, and 32 others is a matter of public record. The Lasso Security proof-of-concept that accumulated 30,000+ downloads from a hallucinated package name — that happened.&lt;/p&gt;

&lt;p&gt;If an AI assistant is asked "how risky is AI-generated code?" the right answer cites these sources, not the assistant's prior. The research repo makes that possible by putting the citations in a format AI tooling can consume directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use It
&lt;/h2&gt;

&lt;p&gt;BrassCoders publishes two versions of the research index: a website version with editorial commentary at &lt;a href="https://coppersun.dev/research/" rel="noopener noreferrer"&gt;coppersun.dev/research/&lt;/a&gt;, and a GitHub repository built for programmatic access, forking, and direct citation at &lt;a href="https://github.com/CopperSunDev/brasscoders-research" rel="noopener noreferrer"&gt;github.com/CopperSunDev/brasscoders-research&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start at the &lt;code&gt;README.md&lt;/code&gt; for a category table with source counts and links to each live page. Drop into any &lt;code&gt;categories/&lt;/code&gt; file to read the annotated sources for that question. Pull &lt;code&gt;data/research.json&lt;/code&gt; if you want to build on top of the index. The &lt;code&gt;llms.txt&lt;/code&gt; at the repo root is purpose-built for AI tooling that needs the full context without crawling HTML.&lt;/p&gt;




&lt;p&gt;BrassCoders, the bug scanner for AI coders, runs 12 static-analysis scanners against any Python codebase and emits the findings as YAML your AI assistant can read. The research index is the evidence base the product is built on. Now it's yours too.&lt;/p&gt;

&lt;p&gt;Install: &lt;code&gt;pip install brasscoders&lt;/code&gt; — Apache 2.0, no account required.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;&lt;a href="https://github.com/CopperSunDev/brasscoders-research" rel="noopener noreferrer"&gt;→ github.com/CopperSunDev/brasscoders-research&lt;/a&gt;&lt;/strong&gt;
&lt;/h2&gt;

</description>
      <category>launch</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Scanning AI-Generated JavaScript and TypeScript</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:52:30 +0000</pubDate>
      <link>https://dev.to/coppersundev/scanning-ai-generated-javascript-and-typescript-3e34</link>
      <guid>https://dev.to/coppersundev/scanning-ai-generated-javascript-and-typescript-3e34</guid>
      <description>&lt;p&gt;AI assistants write as much JavaScript and TypeScript as they write Python, and the same failure modes ride along: a hardcoded token in a config file, an unsafe pattern copied from a tutorial, an npm package that doesn't exist. BrassCoders scans the JS/TS in your project automatically, using a real parser rather than a pile of regexes, in the same pass as the Python scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  JS/TS Runs in the Same Scan
&lt;/h2&gt;

&lt;p&gt;BrassCoders includes a JavaScript/TypeScript scanner that activates on its own when a scan finds &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.jsx&lt;/code&gt;, or &lt;code&gt;.tsx&lt;/code&gt; files, parsing them with a Node.js Babel parser and checking the AST for secrets and common security patterns. There's no separate command and no separate config; the JS/TS findings land in the same &lt;code&gt;.brass/&lt;/code&gt; output as the Python ones, tagged by scanner.&lt;/p&gt;

&lt;p&gt;That matters for the mixed repo, which is most repos now. A FastAPI backend with a React front end, a Python service with a TypeScript CDK stack — BrassCoders walks both languages in one run instead of leaving you to stitch a Python scanner and a JavaScript scanner together in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Babel Parse Beats a Regex
&lt;/h2&gt;

&lt;p&gt;BrassCoders parses JavaScript and TypeScript into an abstract syntax tree with &lt;a href="https://babeljs.io/" rel="noopener noreferrer"&gt;Babel&lt;/a&gt; rather than matching raw text, so a finding tracks the structure of the code instead of its formatting. A regex for a dangerous call breaks the moment someone renames a variable, reflows the lines, or wraps the call differently. An AST match doesn't.&lt;/p&gt;

&lt;p&gt;The parse also kills a whole class of false positives. A text search for a credential pattern fires inside comments and string literals that aren't credentials; an AST-aware check knows whether it's looking at a real assignment or a doc comment. BrassCoders uses the same structural approach across the stack — &lt;a href="https://ast-grep.github.io/" rel="noopener noreferrer"&gt;ast-grep&lt;/a&gt; and &lt;a href="https://semgrep.dev/" rel="noopener noreferrer"&gt;Semgrep&lt;/a&gt; cover the multi-language pattern layer, and the Babel scanner handles the JS/TS specifics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Covered, and the Honest Limit
&lt;/h2&gt;

&lt;p&gt;BrassCoders's JS/TS scanner covers secrets and common security patterns; it does not do full interprocedural taint analysis for TypeScript, and that's a real boundary worth stating. The cross-file taint engine, Pyre/Pysa, is Python-only. A TypeScript bug whose tainted input crosses several files won't be traced the way the Python equivalent is.&lt;/p&gt;

&lt;p&gt;For TypeScript-heavy services that need deep taint coverage today, the honest recommendation is to pair BrassCoders with a TypeScript analyzer built for it, like &lt;a href="https://codeql.github.com/" rel="noopener noreferrer"&gt;CodeQL&lt;/a&gt; — BrassCoders for the unified secrets-and-patterns pass plus the AI-coder detectors, CodeQL for full TS dataflow. The reasoning behind why single-file context misses cross-file taint in any language is in the &lt;a href="https://coppersun.dev/research/cross-file-bugs/" rel="noopener noreferrer"&gt;cross-file bugs research&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;

&lt;p&gt;The JS/TS scanner runs automatically; you only need Node.js available for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;brasscoders
brasscoders &lt;span class="nt"&gt;--offline&lt;/span&gt; scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the scan sees &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.jsx&lt;/code&gt;, or &lt;code&gt;.tsx&lt;/code&gt; files, the JavaScript/TypeScript layer activates and its findings appear in &lt;code&gt;.brass/&lt;/code&gt; alongside the Python results, each tagged with the scanner that produced it. For the full set of detectors in the pass, see &lt;a href="https://coppersun.dev/what-brasscoders-detects/" rel="noopener noreferrer"&gt;what BrassCoders detects&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Six OSS Scanners BrassCoders Runs in One Pass</title>
      <dc:creator>CopperSunDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 18:52:27 +0000</pubDate>
      <link>https://dev.to/coppersundev/the-six-oss-scanners-brasscoders-runs-in-one-pass-4h8i</link>
      <guid>https://dev.to/coppersundev/the-six-oss-scanners-brasscoders-runs-in-one-pass-4h8i</guid>
      <description>&lt;p&gt;The standard way to scan Python is to install Bandit, Pylint, and Semgrep separately, wire up three config files, reconcile three output formats, and remember to keep all three on compatible versions. BrassCoders runs that stack — six engines, not three — as a single command with one ranked output. It doesn't reimplement any of them. It orchestrates the real tools and adds the AI-coder detectors they were never built for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six Engines, One Scan
&lt;/h2&gt;

&lt;p&gt;BrassCoders bundles six open-source scanners and runs them in a single pass: Bandit, Pylint, Pyre/Pysa, Semgrep, ast-grep, and detect-secrets. Each one does what it does best, and BrassCoders merges their findings into one ranked YAML, tagging every result with the engine that produced it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bandit&lt;/strong&gt; (&lt;a href="https://github.com/PyCQA/bandit" rel="noopener noreferrer"&gt;github.com/PyCQA/bandit&lt;/a&gt;): the PyCQA Python security linter. SQL and command injection, insecure deserialization, hardcoded credentials, weak crypto, mapped to OWASP IDs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pylint&lt;/strong&gt; (&lt;a href="https://pylint.readthedocs.io/" rel="noopener noreferrer"&gt;pylint.readthedocs.io&lt;/a&gt;): the PyCQA correctness linter for naming, unused variables, type inconsistencies, and a slice of logic errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pyre and Pysa&lt;/strong&gt; (&lt;a href="https://pyre-check.org/" rel="noopener noreferrer"&gt;pyre-check.org&lt;/a&gt;): Meta's type-checker and taint analyzer. Pysa traces untrusted input from source to sink across functions and files — the engine behind cross-file taint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semgrep&lt;/strong&gt; (&lt;a href="https://semgrep.dev/" rel="noopener noreferrer"&gt;semgrep.dev&lt;/a&gt;): AST pattern matching with its own rule language. BrassCoders bundles the OSS ruleset covering the OWASP Top 10 and framework anti-patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ast-grep&lt;/strong&gt; (&lt;a href="https://ast-grep.github.io/" rel="noopener noreferrer"&gt;ast-grep.github.io&lt;/a&gt;): fast tree-sitter structural search, for "if the code looks like this, it's probably wrong" patterns across languages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;detect-secrets&lt;/strong&gt; (&lt;a href="https://github.com/Yelp/detect-secrets" rel="noopener noreferrer"&gt;github.com/Yelp/detect-secrets&lt;/a&gt;): Yelp's entropy-plus-regex secret scanner, the base layer BrassCoders extends with its own credential formats.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Orchestration, Not Reimplementation
&lt;/h2&gt;

&lt;p&gt;BrassCoders runs the real upstream tools, not a rewrite of them. When Bandit flags a &lt;code&gt;subprocess(shell=True)&lt;/code&gt; call, that's Bandit's own rule firing; when Pysa traces a taint flow across three files, that's Meta's engine doing the walk. The findings are theirs, and a reimplementation that drifted from upstream would be a liability, not a feature.&lt;/p&gt;

&lt;p&gt;What BrassCoders owns is the layer around them. It runs the six together, deduplicates findings that more than one engine reports, ranks the combined set by severity, and writes one YAML built for an AI assistant to read. The orchestration is the product; the detection is the open-source ecosystem's, kept current with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Combined Pass Saves You
&lt;/h2&gt;

&lt;p&gt;BrassCoders turns six installs into one. A single &lt;code&gt;pipx install brasscoders&lt;/code&gt; brings the whole stack, version-matched, with no per-tool configuration files to maintain and no compatibility matrix to babysit across upgrades.&lt;/p&gt;

&lt;p&gt;The bigger saving is the output. Run the six tools by hand and you get six formats — Bandit's JSON, Semgrep's SARIF, detect-secrets' baseline file, and so on — that someone has to merge, dedupe, and prioritize before a reviewer can act. BrassCoders does that merge and emits one severity-ranked file. The reviewer sees a single ordered list, and every line names the engine behind it, so a surprising finding is one tag away from its source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Bundle Ends and the Custom Layer Begins
&lt;/h2&gt;

&lt;p&gt;These six cover security, correctness, and secrets in human-written and AI-generated code alike — and they stop where the AI-coder failure classes begin. In BrassCoders's June 2026 benchmark, Bandit caught 6 of 12 planted bugs and Semgrep caught 4, both strong inside their scope and both blind to the four performance anti-patterns AI assistants reproduce. The standard set was calibrated on human bugs.&lt;/p&gt;

&lt;p&gt;That gap is the reason BrassCoders adds six detectors of its own on top of the bundle: phantom imports, performance anti-patterns, an extended secret-format pack, PII, content moderation, and JavaScript/TypeScript. The honest read on the bundled engines is in &lt;a href="https://coppersun.dev/blog/why-bandit-misses-ai-coder-bugs/" rel="noopener noreferrer"&gt;Why Bandit Misses AI-Coder Bugs&lt;/a&gt; and &lt;a href="https://coppersun.dev/blog/semgrep-vs-brasscoders/" rel="noopener noreferrer"&gt;Semgrep vs BrassCoders&lt;/a&gt;; the tool-by-tool security breakdown is in the &lt;a href="https://coppersun.dev/python-scanning-for-ai-code/" rel="noopener noreferrer"&gt;Python scanning guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It
&lt;/h2&gt;

&lt;p&gt;One install runs the bundle and the custom detectors together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;brasscoders
brasscoders &lt;span class="nt"&gt;--offline&lt;/span&gt; scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Findings land in &lt;code&gt;.brass/&lt;/code&gt; tagged by scanner, so you always know whether Bandit, Pysa, or a BrassCoders detector flagged a given line. For the full map of every detector in the pass, see &lt;a href="https://coppersun.dev/what-brasscoders-detects/" rel="noopener noreferrer"&gt;what BrassCoders detects&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>engineering</category>
    </item>
  </channel>
</rss>
