<?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: Sarthak Narula</title>
    <description>The latest articles on DEV Community by Sarthak Narula (@sarthak_narula_5789cd042d).</description>
    <link>https://dev.to/sarthak_narula_5789cd042d</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%2F1926899%2F777ab9d6-42e1-4a33-8265-86a039f10203.jpeg</url>
      <title>DEV Community: Sarthak Narula</title>
      <link>https://dev.to/sarthak_narula_5789cd042d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sarthak_narula_5789cd042d"/>
    <language>en</language>
    <item>
      <title>Squint: your screenshots have secrets.</title>
      <dc:creator>Sarthak Narula</dc:creator>
      <pubDate>Mon, 26 Oct 2026 06:39:39 +0000</pubDate>
      <link>https://dev.to/sarthak_narula_5789cd042d/squint-your-screenshots-have-secrets-4fd0</link>
      <guid>https://dev.to/sarthak_narula_5789cd042d/squint-your-screenshots-have-secrets-4fd0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We almost do everything with AI now. A bug, an email, a form, a weird error in a dashboard: We just take a screenshot and paste it into a chat. It's the laziest, fastest way to explain anything.&lt;/p&gt;

&lt;p&gt;It's also how we keep leaking things. An email address in the corner of a page. A phone number in a form. An API key sitting in a terminal we didn't bother to crop. By the time we notice, the screenshot is already on someone else's server.&lt;/p&gt;

&lt;p&gt;So I built Squint: a small macOS menu bar app that checks every screenshot before it goes anywhere, using Gemma running on my own Mac.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Take a screenshot the way you always do. If it contains a password, key, token, email, phone number or card number, a banner slides in:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hid 3 things in your screenshot The redacted copy is on your clipboard&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the whole interaction. The clipboard already holds the safe version, so ⌘V just works. No dialog, no export button, no new habit.&lt;/p&gt;

&lt;p&gt;If you want to check what Squint did, click the banner. You see the screenshot exactly as it will be pasted. Click anything to show or hide it, or drag a box over something it missed.&lt;/p&gt;

&lt;p&gt;Everything happens on your Mac: reading the text, deciding what's private, and redacting it. The deciding is done by Gemma 3 (4B), running locally through Ollama.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj6qrjbyc0i11p5gxfomw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj6qrjbyc0i11p5gxfomw.png" alt=" " width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7uwr6dx1uqz4qolgpqx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7uwr6dx1uqz4qolgpqx3.png" alt=" " width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this fits "Slow and Steady"
&lt;/h2&gt;

&lt;p&gt;The laziest privacy tool is the one you never think about. You don't open Squint, you don't drag files into it, and you don't change how you take screenshots. It squints at every screenshot so you don't have to.&lt;/p&gt;

&lt;p&gt;It's lazy on the inside too. Gemma is the sloth of the pipeline. Fast pattern rules catch the obvious cases (a Stripe live key is always a Stripe live key), and Gemma only wakes up for the genuinely ambiguous ones, like "is &lt;a href="mailto:support@acme.io"&gt;support@acme.io&lt;/a&gt; private here, or is it a public address?"&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/PY82pvmH1iQ" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;🌐 &lt;strong&gt;&lt;a href="https://mysquint.tech" rel="noopener noreferrer"&gt;mysquint.tech&lt;/a&gt;&lt;/strong&gt;: download for macOS (Apple Silicon and Intel, macOS 14+)&lt;/p&gt;

&lt;p&gt;Try it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy a screenshot of a &lt;code&gt;.env&lt;/code&gt; file with ⌃⇧⌘4.&lt;/li&gt;
&lt;li&gt;About 1.5 seconds later, the banner appears.&lt;/li&gt;
&lt;li&gt;Paste into ChatGPT. The keys are pixelated; the variable names are still readable.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Marky2813" rel="noopener noreferrer"&gt;
        Marky2813
      &lt;/a&gt; / &lt;a href="https://github.com/Marky2813/squint" rel="noopener noreferrer"&gt;
        squint
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Squint&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Squint checks every screenshot you take on your Mac for passwords, API keys, tokens and
personal details, and puts a redacted copy on your clipboard. Nothing leaves your Mac
text is read with Apple's Vision framework, and Gemma 3 runs locally through Ollama.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;Squint.dmg&lt;/code&gt; and drag Squint into Applications.&lt;/li&gt;
&lt;li&gt;Open Squint. The app is not notarized yet, so the first time macOS blocks it: open
&lt;strong&gt;System Settings → Privacy &amp;amp; Security&lt;/strong&gt; and click &lt;strong&gt;Open Anyway&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The welcome window installs what Squint needs:
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; (click Download if you don't have it), and&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 3&lt;/strong&gt; (3.3 GB, downloaded once with a progress bar).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start Squint&lt;/strong&gt;. Look for the narrowed eye in the menu bar.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When macOS asks whether Squint may access your Desktop, allow it: that's where
screenshots are saved.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Use&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Take a screenshot as usual (⇧⌘4 to save a file, ⌃⇧⌘4 to copy it). If…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Marky2813/squint" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftzb4rxj5j6qpg5esls8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftzb4rxj5j6qpg5esls8y.png" alt="Pipeline inside " width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Catching the screenshot
&lt;/h3&gt;

&lt;p&gt;Squint watches two places: the screenshot folder (⇧⌘4) and the clipboard (⌃⇧⌘4).&lt;/p&gt;

&lt;p&gt;macOS's floating thumbnail delays saving the file by a few seconds, so Squint turns it off while it runs and restores your setting when it quits. It does that even if the app is force-killed: the engine notices its parent process is gone and cleans up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading it
&lt;/h3&gt;

&lt;p&gt;A small Swift helper runs Apple's Vision OCR and returns every line with per-word boxes. It runs on device and takes about 0.3–0.4 s.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh9aaqb3zkmxjau1yfiv0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh9aaqb3zkmxjau1yfiv0.png" alt="Apple Vision's real OCR output drawn onto the .env screenshot: a blue box around every word. A whole line like STRIPE_SECRET_KEY=sk_live_… is a single " width="800" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding candidates
&lt;/h3&gt;

&lt;p&gt;Regex rules sort every match into three tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive:&lt;/strong&gt; AWS, Stripe, GitHub, Slack and Google keys, JWTs, private keys, credentials in URLs, bearer tokens, and card numbers that pass the Luhn check. These are hidden immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambiguous:&lt;/strong&gt; emails, phone numbers, &lt;code&gt;password = …&lt;/code&gt; assignments, high-entropy strings. These go to Gemma.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benign:&lt;/strong&gt; placeholders like &lt;code&gt;&amp;lt;YOUR_API_KEY&amp;gt;&lt;/code&gt;, UUIDs, commit SHAs, dates, &lt;code&gt;noreply@&lt;/code&gt;. These are dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmeshshkxggy7zubiijvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmeshshkxggy7zubiijvj.png" alt="Three columns of examples. Sensitive, hidden immediately: AWS, Stripe, GitHub, Slack, Google keys, JWTs, private keys, URL passwords, bearer tokens, Luhn-valid cards. Ambiguous, Gemma decides: emails, phones, password assignments, random strings. Benign, dropped: placeholders, UUIDs, commit hashes, dates, noreply and support addresses" width="799" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Regex decides where. Gemma decides whether.
&lt;/h3&gt;

&lt;p&gt;Each ambiguous candidate goes to &lt;code&gt;gemma3:4b&lt;/code&gt; with only the matched text and about one line of context, never the image. The output is constrained by a JSON schema to &lt;code&gt;{"sensitive": true|false}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdeo4k8w6gg4ubb55cv74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdeo4k8w6gg4ubb55cv74.png" alt="Where: the OCR line SESSION_SECRET=q8Zr2… with a blue pixel box around the value. Whether: the JSON candidate Gemma receives, and its answer {" width="799" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two design rules made this safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma never locates text.&lt;/strong&gt; OCR and regex decide &lt;em&gt;where&lt;/em&gt;; Gemma only decides &lt;em&gt;whether&lt;/em&gt;. A small model can't put a box in the wrong place if it never draws boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure means hidden.&lt;/strong&gt; If Gemma is slow, offline, or returns garbage, the item is treated as sensitive. The worst case is over-redaction, never a leak.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Gemma 3, not Gemma 4? I built and tuned everything on Gemma 3 4B, including the hand-written chat template and the prompt-cache warm up. I haven’t tested Gemma 4 yet. It’s the first thing I’ll try next, and I’ll re-run the same 24-screenshot eval before switching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Gemma fast
&lt;/h3&gt;

&lt;p&gt;My first version took 3 seconds per screenshot. Here's what fixed it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One call per candidate, run in parallel&lt;/strong&gt; (4 slots), instead of one big batch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Few-shot examples in the prompt&lt;/strong&gt;, which fixed accuracy. Gemma 3 &lt;strong&gt;1B&lt;/strong&gt; flagged nearly everything: it scored 43% on a 14-case smoke test. &lt;strong&gt;4B&lt;/strong&gt; was the sweet spot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt-prefix caching.&lt;/strong&gt; Gemma 3 uses sliding-window attention, so llama.cpp could only reuse its prompt cache from checkpoints, and every call was re-reading the instructions. I switched Ollama to &lt;code&gt;raw&lt;/code&gt; mode with Gemma's chat template written by hand, and warm every slot with the shared prefix at startup. Now only the candidate itself is new work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdw8ytr9m0gmmepid3tif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdw8ytr9m0gmmepid3tif.png" alt="Four parallel slots, each a long cached bar (" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result: &lt;strong&gt;about 0.36 s per candidate&lt;/strong&gt;, and about &lt;strong&gt;1.5 s from screenshot to safe clipboard&lt;/strong&gt;. That kind of tuning is only possible because the weights and the runtime are open: I could change the template and the cache behaviour, not just the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redacting so it can't be undone
&lt;/h3&gt;

&lt;p&gt;Pixelation and blur can sometimes be reversed. Squint first replaces every hidden region with random noise tinted from the surrounding pixels, &lt;em&gt;then&lt;/em&gt; pixelates it, so nothing of the original survives. A property test checks that two different secrets produce identical redacted pixels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj7oq5t5oa7w9ra2zf1g3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj7oq5t5oa7w9ra2zf1g3.png" alt="Real crops: a Stripe key, the same box replaced by tinted noise, then pixelated. Below, two different keys produce redacted images with 0 pixels different" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hiding only what matters
&lt;/h3&gt;

&lt;p&gt;OCR gives one box per word, and &lt;code&gt;STRIPE_KEY=sk_live_…&lt;/code&gt; is one "word". Squint works out where the value sits inside that word, so the variable name stays readable and only the value is hidden. You can still tell &lt;em&gt;what&lt;/em&gt; was there, just not &lt;em&gt;what it was&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjae8ddnqy79nu62wa55g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjae8ddnqy79nu62wa55g.png" alt="Top: Vision returns one dashed box around the whole STRIPE_SECRET_KEY=sk_live_… line. Bottom: Squint's real output covers only the value" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring it
&lt;/h3&gt;

&lt;p&gt;I built an eval set of 24 generated screenshots: &lt;code&gt;.env&lt;/code&gt; files, YAML, terminals, chat, git logs, dashboards and READMEs, in three fonts, light and dark themes, all with fake secrets. Because I render them myself, I know the exact pixel box of every secret. So the eval checks whether each secret is &lt;strong&gt;fully covered&lt;/strong&gt;, not just detected. Five screenshots are held out and never tuned against.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwjve4flypf7ltt53l4b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpwjve4flypf7ltt53l4b.png" alt="All 24 eval screenshots in a grid, five outlined in blue as held out, with headline numbers: 100% hidden and 100% fully covered on the tuning set, 92% hidden on held-out, 100% of variable names left readable" width="800" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Tuning set (19)&lt;/th&gt;
&lt;th&gt;Held-out (5)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Secrets hidden&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100%&lt;/strong&gt; (39/39)&lt;/td&gt;
&lt;td&gt;92% (11/12)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fully covered, no visible edges&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variable names left readable&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hidden items that were really secrets&lt;/td&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The eval earned its keep. It caught two bugs where part of a secret stayed visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OCR misread one character in the middle of a key, the regex stopped there, and the tail stayed visible. Boxes now always run to the end of the token.&lt;/li&gt;
&lt;li&gt;OCR read the &lt;code&gt;.&lt;/code&gt; in a JWT as a space, so only half of it was hidden. Squint now joins the pieces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recall on the tuning set went from 74% to 100%. The held-out miss is a JWT in a proportional font that was 95% covered. That's next on the list.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Gemma adds
&lt;/h3&gt;

&lt;p&gt;With Gemma switched off, Squint has to hide every ambiguous match. Across all 24 screenshots:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1cakur79v57h1cj9g5uu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1cakur79v57h1cj9g5uu.png" alt="Bar chart of false alarms: regex only 10, regex plus Gemma 4. Secrets hidden stay at 50 of 51 either way; Gemma adds about 1 s" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Regex only&lt;/th&gt;
&lt;th&gt;Regex + Gemma&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;False alarms&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets hidden&lt;/td&gt;
&lt;td&gt;50 / 51&lt;/td&gt;
&lt;td&gt;50 / 51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra time&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;about 1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same recall, less than half the false alarms. Real screenshots are messier than my synthetic ones, and that's exactly where context matters most: &lt;code&gt;support@&lt;/code&gt; versus a colleague's personal address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8rzil8tbwbp7un5dqk4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8rzil8tbwbp7un5dqk4v.png" alt="Everything inside " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because the screenshot is the secret.&lt;/strong&gt; A cloud model would be a better OCR engine and a smarter classifier, and it would be useless here. You can't upload a screenshot to someone else's server to ask whether it's safe to upload screenshots to someone else's server. The only honest design is a model that runs where the screenshot already is, and open weights make that possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because nothing leaves your Mac.&lt;/strong&gt; After the one-time model download, Squint makes no network calls except to Ollama on &lt;code&gt;127.0.0.1&lt;/code&gt;. The review API is bound to localhost and needs a per-run token. There are no accounts, analytics or update checks; the only outside link is the Ollama download page, which the setup window opens in your browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because it works offline.&lt;/strong&gt; On a plane, on a locked-down work laptop, anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because you shouldn't need an API key to protect your API keys.&lt;/strong&gt; No account, no subscription, no usage bill. Set it up once and it's free forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;Before Squint, a safe screenshot meant: take it, open it, scan every corner, crop or scribble over anything private, then paste. I usually skipped all of that, which is exactly how keys and emails leaked.&lt;/p&gt;

&lt;p&gt;With Squint, the safe path and the lazy path are the same path. Take the screenshot, press ⌘V. About 1.5 seconds later, the clipboard already holds the redacted copy. Zero extra steps, zero new habits, and the check I used to skip now happens every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 3 4B is the decision-maker. It runs locally via Ollama with schema-constrained output and prompt-prefix caching tuned for Gemma 3's sliding-window attention, and its contribution is measured against a regex-only baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of a .tech Domain:&lt;/strong&gt; &lt;a href="https://mysquint.tech" rel="noopener noreferrer"&gt;mysquint.tech&lt;/a&gt; is the landing page and download, and it hosts every image in this post.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
