<?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: Sumeet Gupta</title>
    <description>The latest articles on DEV Community by Sumeet Gupta (@sumeet_gupta_28f5b7b8ece8).</description>
    <link>https://dev.to/sumeet_gupta_28f5b7b8ece8</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%2F1709833%2Fbe42d289-28f1-4a62-8b4f-7308464c31f0.png</url>
      <title>DEV Community: Sumeet Gupta</title>
      <link>https://dev.to/sumeet_gupta_28f5b7b8ece8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sumeet_gupta_28f5b7b8ece8"/>
    <language>en</language>
    <item>
      <title>How We Reduced DLP False Positives by 80% Using Entropy Gating</title>
      <dc:creator>Sumeet Gupta</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:03:43 +0000</pubDate>
      <link>https://dev.to/sumeet_gupta_28f5b7b8ece8/how-we-reduced-dlp-false-positives-by-80-using-entropy-gating-5h52</link>
      <guid>https://dev.to/sumeet_gupta_28f5b7b8ece8/how-we-reduced-dlp-false-positives-by-80-using-entropy-gating-5h52</guid>
      <description>&lt;p&gt;We've been building &lt;a href="https://spidercob.com" rel="noopener noreferrer"&gt;Spidercob&lt;/a&gt;, an enterprise DLP platform. The hardest problem was never detecting sensitive data it was &lt;em&gt;not&lt;/em&gt; detecting things that aren't sensitive.&lt;/p&gt;

&lt;p&gt;Regex-based scanners are easy to write. A pattern for AWS access keys takes five minutes. The problem is that same pattern fires on any 20-character uppercase string in your codebase test fixtures, documentation examples, README placeholders, all flagged as critical.&lt;/p&gt;

&lt;p&gt;After six months of tuning in production we extracted our detection engine into a standalone library:&lt;br&gt;
  &lt;strong&gt;&lt;a href="https://github.com/SpiderCob/dlp-patterns" rel="noopener noreferrer"&gt;dlp-patterns&lt;/a&gt;&lt;/strong&gt;. Here's what actually works.&lt;/p&gt;

&lt;p&gt;## The Three Layers&lt;/p&gt;

&lt;p&gt;### 1. Validators&lt;/p&gt;

&lt;p&gt;The first thing we added was format validation &lt;em&gt;after&lt;/em&gt; the regex match. A regex is a necessary condition, not a sufficient one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credit cards&lt;/strong&gt; - every match runs through Luhn's algorithm. A transposed digit fails Luhn. This alone cuts credit card false positives by roughly 90%.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
python
  def _luhn_check(number: str) -&amp;gt; bool:
      digits = [int(d) for d in number if d.isdigit()]
      odd_digits = digits[-1::-2]
      even_digits = digits[-2::-2]
      total = sum(odd_digits)
      for d in even_digits:
          total += sum(divmod(d * 2, 10))
      return total % 10 == 0

  SSNs — the IRS publishes rules about which area codes are invalid. 000, 666, and 900-999 are all unassigned. We reject those before surfacing a finding.

  JWTs — we base64-decode the header and payload and verify they parse as valid JSON with expected fields (alg, typ). A random string that happens to match the JWT pattern fails this check immediately.

  2. Entropy Gating

  This is the biggest lever we found. Low-entropy strings are almost never real secrets.

  Shannon entropy measures how random a string is. A real AWS secret key looks like noise. A test placeholder like AKIAIOSFODNN7EXAMPLE —
  ironically AWS's own documentation example — has lower entropy because it contains recognisable English words.

  We use a sliding window approach:

  import math

  def _shannon_entropy(s: str) -&amp;gt; float:
      if not s:
          return 0.0
      freq = {}
      for c in s:
          freq[c] = freq.get(c, 0) + 1
      length = len(s)
      return -sum(
          (count / length) * math.log2(count / length)
          for count in freq.values()
      )

  def _passes_entropy_gate(value: str, min_entropy: float = 3.5) -&amp;gt; bool:
      if _shannon_entropy(value) &amp;lt; min_entropy:
          return False
      # Sliding window catches strings like "aaabbbccc" that fool overall entropy
      window = 8
      for i in range(len(value) - window + 1):
          chunk = value[i:i + window]
          if len(set(chunk)) &amp;lt; 4:
              return False
      return True

  This single check eliminates the vast majority of generic secret false positives. Repeated characters, dictionary words, sequential patterns —
  all rejected without touching a single regex.

  3. Context Scoring

  The last layer doesn't reject findings outright — it scores them.

  Every finding gets a context_score between 0.0 and 1.0 based on the words surrounding it. We scan ±100 characters around each match:

  Boost keywords — score goes up: production, prod, secret, credential, deploy, live, api_key

  Penalty keywords — score goes down: example, placeholder, test, sample, dummy, fake, replace

  BOOST_KEYWORDS = {"production", "prod", "secret", "credential", "deploy", "live"}
  PENALTY_KEYWORDS = {"example", "placeholder", "test", "sample", "dummy", "fake"}

  def _context_score(text: str, match_start: int, match_end: int) -&amp;gt; float:
      window_start = max(0, match_start - 100)
      window_end = min(len(text), match_end + 100)
      context = text[window_start:window_end].lower()
      words = set(context.split())

      score = 0.5  # neutral baseline
      score += len(words &amp;amp; BOOST_KEYWORDS) * 0.15
      score -= len(words &amp;amp; PENALTY_KEYWORDS) * 0.20
      return max(0.0, min(1.0, score))

  You can threshold on context_score in your pipeline. In CI, only block on findings with context_score &amp;gt; 0.7. In a compliance scan, report
  everything but prioritise high-score findings.

  Required Context Keywords

  Some patterns are too ambiguous to fire without corroborating evidence.

  ICD-10 codes like E11.9 are a good example. The pattern [A-Z]\d{2}\.\d{1,4} matches thousands of strings that aren't medical codes. We only fire
   the ICD-10 pattern when medical keywords appear nearby:

  diagnosis, icd, icd-10, condition, disease, disorder,
  patient, clinical, medical, treatment

  Without one of those words in context the pattern stays silent. This approach works well for any structurally ambiguous pattern — Telegram bot
  tokens, NPI numbers, DEA registration numbers.

  Using It

  import dlp_patterns

  result = dlp_patterns.scan(text)

  result.has_findings          # bool
  result.highest_severity      # "CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | None
  result.critical              # list[Finding]

  f = result.critical[0]
  f.type                       # "aws_access_key"
  f.value                      # masked value
  f.context_score              # 0.0 – 1.0
  f.context_keywords_found     # ["production", "deploy"]

  # Redact in one line
  clean = dlp_patterns.redact(text)

  # Secrets only — skip PII, faster for CI
  result = dlp_patterns.scan(code, secrets_only=True)

  CLI for pipelines:

  # Fail CI on CRITICAL findings
  dlp-scan -secrets-only src/

  # Redact logs before shipping to external services
  cat app.log | dlp-scan -redact &amp;gt; safe.log

  # JSON output for SIEM integration
  dlp-scan -json document.txt | tee dlp-report.json

  GitHub Actions — use the official action:

  - name: DLP Secret Scan
    uses: spidercob/dlp-scan-action@v1
    with:
      secrets-only: 'true'
      fail-on: 'critical'

  50+ Pattern Categories

  ┌────────────────┬────────────────────────────────────────────────────────────────────────┐
  │    Category    │                                Patterns                                │
  ├────────────────┼────────────────────────────────────────────────────────────────────────┤
  │ Secrets        │ AWS, GitHub PATs, Stripe, SendGrid, Slack, Twilio, Discord, Google API │
  ├────────────────┼────────────────────────────────────────────────────────────────────────┤
  │ Infrastructure │ DB connection strings, hardcoded passwords, Docker registry auth       │
  ├────────────────┼────────────────────────────────────────────────────────────────────────┤
  │ Crypto keys    │ RSA / EC / SSH / PGP private keys, X.509 certs                         │
  ├────────────────┼────────────────────────────────────────────────────────────────────────┤
  │ PII            │ SSN, credit cards, email, US phone, passport, date of birth            │
  ├────────────────┼────────────────────────────────────────────────────────────────────────┤
  │ Healthcare     │ Medical record numbers, ICD-10 codes, NPI, DEA numbers                 │
  ├────────────────┼────────────────────────────────────────────────────────────────────────┤
  │ Crypto wallets │ Bitcoin addresses, Ethereum addresses                                  │
  └────────────────┴────────────────────────────────────────────────────────────────────────┘

  Install

  pip install dlp-patterns

  Zero dependencies. Python 3.9+. Apache 2.0.

  Source: https://github.com/SpiderCob/dlp-patterns

  GitHub Action: https://github.com/SpiderCob/dlp-scan-action

  If you're scanning at scale and need dashboards, audit logs, ICAP proxy integration, or Gmail/Slack connectors — that's what
  https://spidercob.com is built for.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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