<?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: YUICHI KANEKO</title>
    <description>The latest articles on DEV Community by YUICHI KANEKO (@yuichi).</description>
    <link>https://dev.to/yuichi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3854598%2F6df92f63-f347-4a6c-87fc-e2899235cfb9.png</url>
      <title>DEV Community: YUICHI KANEKO</title>
      <link>https://dev.to/yuichi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuichi"/>
    <language>en</language>
    <item>
      <title>KeyGate: A Fast Pre-Commit Guardrail Against Secret Leaks</title>
      <dc:creator>YUICHI KANEKO</dc:creator>
      <pubDate>Fri, 24 Apr 2026 05:06:55 +0000</pubDate>
      <link>https://dev.to/yuichi/keygate-a-fast-pre-commit-guardrail-against-secret-leaks-1i7o</link>
      <guid>https://dev.to/yuichi/keygate-a-fast-pre-commit-guardrail-against-secret-leaks-1i7o</guid>
      <description>&lt;p&gt;Accidentally committing an API key, password, or private key is still one of the easiest ways to create a serious security incident.&lt;/p&gt;

&lt;p&gt;The risk gets worse as development speeds up: larger diffs, faster iteration, and more code drafted by AI coding agents before a human reviews every line.&lt;/p&gt;

&lt;p&gt;That is why I built &lt;strong&gt;keygate&lt;/strong&gt;: a fast local pre-commit guardrail that scans &lt;strong&gt;only staged added lines&lt;/strong&gt; and blocks likely secrets before they enter Git history.&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;keygate
keygate activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;keygate&lt;/code&gt; now runs automatically before every &lt;code&gt;git commit&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/kanekoyuichi/keygate" rel="noopener noreferrer"&gt;https://github.com/kanekoyuichi/keygate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/keygate/" rel="noopener noreferrer"&gt;https://pypi.org/project/keygate/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: MIT&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Accidentally writing an API key directly into code during development happens to everyone. The real problem is that once you &lt;code&gt;git commit&lt;/code&gt; it, &lt;strong&gt;the value becomes part of Git history permanently&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even if you &lt;code&gt;git rm&lt;/code&gt; it or force-push, the old SHA can still be used to retrieve it&lt;/li&gt;
&lt;li&gt;Once pushed to GitHub, bots can scrape it within seconds&lt;/li&gt;
&lt;li&gt;An AWS key can lead to a massive bill; an OpenAI key can drain your usage quota almost instantly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I needed a tool to &lt;strong&gt;stop this at the moment of commit&lt;/strong&gt;. Existing tools like Gitleaks and TruffleHog are excellent, but they focus on full repository scanning and CI workflows. I wanted something optimized specifically for the local pre-commit experience.&lt;/p&gt;

&lt;p&gt;More importantly, as we move into a world where AI agents write code, the need for an automatic check right before a commit only increases.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AI agent angle
&lt;/h2&gt;

&lt;p&gt;AI coding agents like Claude Code or Codex can generate large diffs quickly. The safest assumption is not that the agent is malicious, but that speed increases the chance of unnoticed sensitive values reaching a commit.&lt;/p&gt;

&lt;p&gt;Specifically, AI agents tend to create situations like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating code that references &lt;code&gt;.env&lt;/code&gt; or config examples and including their values&lt;/li&gt;
&lt;li&gt;Expanding sample values from READMEs or test fixtures as-is&lt;/li&gt;
&lt;li&gt;Inferring and completing &lt;code&gt;api_key&lt;/code&gt; or &lt;code&gt;password&lt;/code&gt;-looking values from surrounding context&lt;/li&gt;
&lt;li&gt;Producing large diffs in a single pass, before a human has a chance to review every line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A local guardrail becomes more valuable in that workflow, not less. That is why &lt;code&gt;keygate&lt;/code&gt; is designed so that &lt;strong&gt;whether the code was written by a human or an AI, it applies the same check right before the commit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rather than a tool that only works for developers who carefully read the README, &lt;code&gt;keygate&lt;/code&gt; provides JSON output and an agent-specific execution mode so that agents themselves can read the scan results and suggest fixes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What keygate detects
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;keygate&lt;/code&gt; combines multiple signals instead of relying on a single regex:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule-based detection (known formats)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS access keys (&lt;code&gt;AKIA*&lt;/code&gt; / &lt;code&gt;ASIA*&lt;/code&gt; / &lt;code&gt;AROA*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;OpenAI API keys (&lt;code&gt;sk-*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;GitHub tokens (&lt;code&gt;ghp_*&lt;/code&gt;, fine-grained PATs)&lt;/li&gt;
&lt;li&gt;Slack tokens (&lt;code&gt;xoxb-*&lt;/code&gt; / &lt;code&gt;xoxp-*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Stripe keys (&lt;code&gt;sk_live_*&lt;/code&gt; / &lt;code&gt;rk_live_*&lt;/code&gt; / &lt;code&gt;pk_live_*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;SendGrid keys (&lt;code&gt;SG.*.*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;JWTs and PEM private keys (RSA / OpenSSH)&lt;/li&gt;
&lt;li&gt;URL credentials (&lt;code&gt;postgres://user:pass@host&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Values like &lt;code&gt;pk_live_*&lt;/code&gt; (which are meant to be public) or already-masked URL credentials like &lt;code&gt;postgres://user:***@host&lt;/code&gt; are treated as &lt;strong&gt;WARN&lt;/strong&gt; rather than immediately BLOCK. The goal is to catch dangerous things without blocking every documentation-friendly string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entropy detection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strings longer than 20 characters with Shannon entropy above 4.0–4.5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Context scoring&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variable names like &lt;code&gt;api_key&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;secret_token&lt;/code&gt; are tiered into HIGH and MID&lt;/li&gt;
&lt;li&gt;Paths like &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;config.yaml&lt;/code&gt;, &lt;code&gt;settings.py&lt;/code&gt; are tiered similarly&lt;/li&gt;
&lt;li&gt;Assignment syntax (&lt;code&gt;NAME = "..."&lt;/code&gt; / &lt;code&gt;export NAME=...&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How scoring works
&lt;/h2&gt;

&lt;p&gt;Instead of a binary match, &lt;code&gt;keygate&lt;/code&gt; aggregates independent signals into a final score:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Regex rule match&lt;/td&gt;
&lt;td&gt;+50 to +100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High entropy&lt;/td&gt;
&lt;td&gt;+20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword (HIGH): &lt;code&gt;secret&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;api_key&lt;/code&gt;, etc.&lt;/td&gt;
&lt;td&gt;+25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword (MID): &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;credential&lt;/code&gt;, &lt;code&gt;auth&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;+15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assignment syntax &lt;code&gt;NAME = "..."&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;+15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Very sensitive path (&lt;code&gt;.env&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;+20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensitive path (&lt;code&gt;settings/&lt;/code&gt;, &lt;code&gt;config/&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;td&gt;+15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test file&lt;/td&gt;
&lt;td&gt;-10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;example&lt;/code&gt;, &lt;code&gt;dummy&lt;/code&gt;, etc.&lt;/td&gt;
&lt;td&gt;-20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is also a &lt;strong&gt;combo bonus&lt;/strong&gt;: even when no regex rule matches, if multiple signals fire together, an additional bonus applies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;keyword(HIGH/MID) + entropy&lt;/code&gt; → +15&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;keyword(HIGH) + entropy + assignment syntax&lt;/code&gt; → additional +15&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means an unknown secret format can still reach BLOCK if it has a suspicious variable name, random-looking characters, and assignment syntax.&lt;/p&gt;

&lt;p&gt;When a known regex rule does match, the combo bonus is not stacked on top — the rule's own weight is used instead. This keeps the score explainable and avoids inflating it unnecessarily.&lt;/p&gt;

&lt;p&gt;The final verdict:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;block&lt;/code&gt; at 70+&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;warn&lt;/code&gt; at 40–69&lt;/li&gt;
&lt;li&gt;ignored below 40&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example output
&lt;/h2&gt;

&lt;p&gt;When a likely secret is found, the commit is stopped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[BLOCK] High confidence secret detected

File: config.py:12
Rule: aws-access-key
Score: 100

Reason:
AWS Access Key detected; sensitive context detected

Remediation:
  - Remove the key from the code
  - Rotate the AWS credentials immediately
  - Use environment variables or AWS IAM roles instead

To ignore:
  Add comment: # keygate: ignore reason="..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each finding includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;File&lt;/code&gt; — the file and line number&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Rule&lt;/code&gt; — which detection rule fired&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Score&lt;/code&gt; — severity (70+ blocks, 40–69 warns)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Remediation&lt;/code&gt; — concrete steps to fix it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the top of the output, a machine-readable summary line is also emitted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[KEYGATE] status=block findings=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it easy for scripts or agents to parse the outcome without needing JSON mode.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detection accuracy (internal evaluation)
&lt;/h2&gt;

&lt;p&gt;Measured against a labeled corpus of 100 samples (50 known secrets + 50 benign strings):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Recall (real secrets detected)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precision (detected items that were real secrets)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80.6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;True Positives&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False Negatives (missed secrets)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False Positives (benign strings flagged)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;True Negatives&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The primary goal was to get &lt;strong&gt;False Negatives to zero&lt;/strong&gt;. Missing a real secret is far more dangerous than an occasional extra prompt.&lt;/p&gt;

&lt;p&gt;The 12 false positives included: masked URL credentials, placeholders, Stripe publishable keys, and empty &lt;code&gt;API_KEY=&lt;/code&gt; assignments. These are not real secrets, but they look enough like secrets that surfacing them before commit is intentional — they can be suppressed individually with inline ignores, allowlists, or a baseline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Built for developers and coding agents
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;keygate&lt;/code&gt; provides JSON output alongside human-readable CLI output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;keygate scan &lt;span class="nt"&gt;--format&lt;/span&gt; json
keygate scan &lt;span class="nt"&gt;--json&lt;/span&gt;
keygate scan &lt;span class="nt"&gt;--profile&lt;/span&gt; agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--format json&lt;/code&gt; outputs only JSON to stdout&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--json&lt;/code&gt; is an alias for the above&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--profile agent&lt;/code&gt; is a fixed mode for AI agents that always returns JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JSON schema is stable: &lt;code&gt;schema_version&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;summary&lt;/code&gt;, &lt;code&gt;findings[]&lt;/code&gt;. Each finding includes &lt;code&gt;rule_id&lt;/code&gt;, &lt;code&gt;policy&lt;/code&gt;, &lt;code&gt;score&lt;/code&gt;, &lt;code&gt;verdict&lt;/code&gt;, &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;line&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;, and a masked &lt;code&gt;snippet&lt;/code&gt; when available.&lt;/p&gt;

&lt;p&gt;This is not JSON bolted on as an afterthought. It is designed from the start so that &lt;strong&gt;an agent can re-run the scan, parse the output mechanically, and propose fixes&lt;/strong&gt; — closing the loop after a commit is blocked.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;keygate&lt;/code&gt; also has a Claude Code plugin, so Claude can scan staged changes for secrets automatically before commits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling false positives without breaking flow
&lt;/h2&gt;

&lt;p&gt;A secret scanner is only useful if developers can live with it every day.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;keygate&lt;/code&gt; includes three escape hatches for expected findings:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Inline ignore (per line)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;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;dummy-key-for-testing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# keygate: ignore reason="test data"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;reason&lt;/code&gt; is required — so the intent is always documented in the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Allowlist (project-wide)
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;keygate.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[allowlist]&lt;/span&gt;
&lt;span class="py"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"vendor/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"third_party/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;patterns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"dummy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"example"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: adding &lt;code&gt;tests/*&lt;/code&gt; to the allowlist wholesale is not recommended — it would suppress real secrets that accidentally end up in test files.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Baseline (freeze existing findings)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;keygate baseline create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saves the current findings to &lt;code&gt;.keygate.baseline.json&lt;/code&gt; as SHA-256 fingerprints. From that point, the same finding at the same location is suppressed. &lt;strong&gt;The raw secret value is never stored&lt;/strong&gt;, so the baseline file is safe to commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fingerprint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e5282a7860678bc768d280eb3e77d2ca8a44286357c743dd024d74fe0605fe09"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/app/config.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"line_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rule_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"url-credentials"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-22T09:30:00+00:00"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add new findings to an existing baseline: &lt;code&gt;keygate baseline update&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the baseline is committed to the repository, a new team member who runs &lt;code&gt;pipx install keygate &amp;amp;&amp;amp; keygate activate&lt;/code&gt; will automatically pick up the same baseline.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it is different from Gitleaks or TruffleHog
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;keygate&lt;/code&gt; is not a replacement for full repository, history, CI, or cloud secret scanning.&lt;/p&gt;

&lt;p&gt;It is intentionally narrower: a lightweight local guardrail for the moment right before a commit is created.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;keygate&lt;/td&gt;
&lt;td&gt;Fast local pre-commit checks on staged changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gitleaks&lt;/td&gt;
&lt;td&gt;Full repository, history, CI, and configurable rule scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TruffleHog&lt;/td&gt;
&lt;td&gt;Deep secret discovery and verification workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;keygate&lt;/code&gt; when you want a small commit-time check that developers will actually keep enabled.&lt;/p&gt;




&lt;h2&gt;
  
  
  What keygate intentionally does not do
&lt;/h2&gt;

&lt;p&gt;These were explicit non-goals during design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full repository scanning (not the job of a pre-commit hook)&lt;/li&gt;
&lt;li&gt;LLM-based judgment (offline, fast, and deterministic behavior takes priority)&lt;/li&gt;
&lt;li&gt;External API validation (no checking whether a token is actually valid)&lt;/li&gt;
&lt;li&gt;IDE plugins, SaaS integrations, or automatic secret rotation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The primary constraint is completing within 200–500ms locally, every single commit. No LLM calls or external API lookups. For server-side protection, &lt;code&gt;keygate&lt;/code&gt; is meant to complement — not replace — pre-receive hooks and CI-level scanning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;keygate&lt;/code&gt; is a last-line-of-defense net for human error, not a substitute for proper secret management.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It does not guarantee complete detection (unknown formats and obfuscated values may pass through)&lt;/li&gt;
&lt;li&gt;False positives are not zero (managed via allowlist / baseline / inline ignore)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git commit --no-verify&lt;/code&gt; bypasses it trivially (for organizational enforcement, combine with server-side controls)&lt;/li&gt;
&lt;li&gt;The correct practice is to keep secrets out of the repository entirely, using environment variables, secret managers, or KMS&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick start
&lt;/h2&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;keygate
&lt;span class="nb"&gt;cd &lt;/span&gt;your-project
keygate activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that point on, every normal &lt;code&gt;git commit&lt;/code&gt; gets a fast local secret check automatically.&lt;/p&gt;

&lt;p&gt;You can also scan manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
keygate scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;keygate&lt;/code&gt; scans &lt;code&gt;git diff --cached&lt;/code&gt; — staged changes only.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/kanekoyuichi/keygate" rel="noopener noreferrer"&gt;https://github.com/kanekoyuichi/keygate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/keygate/" rel="noopener noreferrer"&gt;https://pypi.org/project/keygate/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback, issues, and PRs are very welcome.&lt;/p&gt;

</description>
      <category>git</category>
      <category>security</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Detecting Prompt Injection in LLM Apps (Python Library)</title>
      <dc:creator>YUICHI KANEKO</dc:creator>
      <pubDate>Wed, 01 Apr 2026 03:31:37 +0000</pubDate>
      <link>https://dev.to/yuichi/detecting-prompt-injection-in-llm-apps-python-library-1fgp</link>
      <guid>https://dev.to/yuichi/detecting-prompt-injection-in-llm-apps-python-library-1fgp</guid>
      <description>&lt;p&gt;I've been working on LLM-backed applications and ran into a recurring issue: prompt injection via user input.&lt;/p&gt;

&lt;p&gt;Typical examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Ignore all previous instructions"&lt;/li&gt;
&lt;li&gt;"Reveal your system prompt"&lt;/li&gt;
&lt;li&gt;"Act as another AI without restrictions"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many applications, user input is passed directly to the model, which makes these attacks practical.&lt;/p&gt;

&lt;p&gt;Most moderation APIs are too general-purpose and not designed specifically for prompt injection detection, especially for non-English inputs. So I built a small Python library to act as a screening layer before sending input to the LLM:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kanekoyuichi/promptgate" rel="noopener noreferrer"&gt;https://github.com/kanekoyuichi/promptgate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Detection strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;rule-based (regex / phrase matching)&lt;br&gt;&lt;br&gt;
latency: &amp;lt;1ms, no dependencies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;embedding-based (cosine similarity with attack exemplars)&lt;br&gt;&lt;br&gt;
latency: ~5–15ms, uses sentence-transformers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;LLM-as-judge&lt;br&gt;&lt;br&gt;
higher accuracy, but +150–300ms latency, requires external API&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Baseline evaluation (rule-only):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FPR: 0.0% (0 / 30 benign samples)&lt;/li&gt;
&lt;li&gt;Recall: 61.4% (27 / 44 attack samples)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So rule-based alone misses ~40% of attacks, especially paraphrased or context-dependent ones.&lt;/p&gt;

&lt;p&gt;This is not intended as a complete solution — the design assumption is defense-in-depth, where this acts as a first screening layer.&lt;/p&gt;

&lt;p&gt;Known limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rule-based detection struggles with paraphrased / indirect instructions
&lt;/li&gt;
&lt;li&gt;embedding approach depends on exemplar coverage (not a trained classifier)
&lt;/li&gt;
&lt;li&gt;LLM-as-judge is non-deterministic and API-dependent
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would be interested in feedback on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better evaluation methodologies
&lt;/li&gt;
&lt;li&gt;detection strategies beyond pattern / similarity / LLM judging
&lt;/li&gt;
&lt;li&gt;how others are handling prompt injection at the application layer
&lt;/li&gt;
&lt;/ul&gt;

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