跳到正文
DEV Community· Sarthak Narula·· 2 小時前AI 評分57

Squint 在 Mac 上自動檢查並遮蔽截圖中的敏感資料

Squint: your screenshots have secrets.

AI 導讀

作者開發的 macOS 選單列工具 Squint(mysquint.tech)會檢查截圖,並將遮蔽敏感內容的版本放入剪貼簿。OCR 與規則負責定位候選內容,Gemma 3 (4B) 只在本機判斷模糊項目,圖片不會離開 Mac。在作者製作的 24 張合成截圖評測中,加入 Gemma 後隱藏的秘密仍為 50/51,誤報由 10 次降至 4 次,額外耗時約 1 秒。

正文

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady

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.

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.

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.

What I Built

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:

Hid 3 things in your screenshot The redacted copy is on your clipboard

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

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.

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.

Before:

After:

Why this fits "Slow and Steady"

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.

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 support@acme.io private here, or is it a public address?"

Demo

🌐 mysquint.tech: download for macOS (Apple Silicon and Intel, macOS 14+)

Try it:

  1. Copy a screenshot of a .env file with ⌃⇧⌘4.
  2. About 1.5 seconds later, the banner appears.
  3. Paste into ChatGPT. The keys are pixelated; the variable names are still readable.

Code

Squint

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.

Install

  1. Open Squint.dmg and drag Squint into Applications.
  2. Open Squint. The app is not notarized yet, so the first time macOS blocks it: open System Settings → Privacy & Security and click Open Anyway.
  3. The welcome window installs what Squint needs:
    • Ollama (click Download if you don't have it), and
    • Gemma 3 (3.3 GB, downloaded once with a progress bar).
  4. Click Start Squint. Look for the narrowed eye in the menu bar.

When macOS asks whether Squint may access your Desktop, allow it: that's where screenshots are saved.

Use

Take a screenshot as usual (⇧⌘4 to save a file, ⌃⇧⌘4 to copy it). If…

How I Built It

Pipeline inside

Catching the screenshot

Squint watches two places: the screenshot folder (⇧⌘4) and the clipboard (⌃⇧⌘4).

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.

Reading it

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.

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

Finding candidates

Regex rules sort every match into three tiers:

  • Sensitive: 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.
  • Ambiguous: emails, phone numbers, password = … assignments, high-entropy strings. These go to Gemma.
  • Benign: placeholders like <YOUR_API_KEY>, UUIDs, commit SHAs, dates, noreply@. These are dropped.

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

Regex decides where. Gemma decides whether.

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

Where: the OCR line SESSION_SECRET=q8Zr2… with a blue pixel box around the value. Whether: the JSON candidate Gemma receives, and its answer {

Two design rules made this safe:

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

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.

Making Gemma fast

My first version took 3 seconds per screenshot. Here's what fixed it:

  • One call per candidate, run in parallel (4 slots), instead of one big batch.
  • Few-shot examples in the prompt, which fixed accuracy. Gemma 3 1B flagged nearly everything: it scored 43% on a 14-case smoke test. 4B was the sweet spot.
  • Prompt-prefix caching. 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 raw 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.

Four parallel slots, each a long cached bar (

Result: about 0.36 s per candidate, and about 1.5 s from screenshot to safe clipboard. 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.

Redacting so it can't be undone

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

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

Hiding only what matters

OCR gives one box per word, and STRIPE_KEY=sk_live_… 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 what was there, just not what it was.

Top: Vision returns one dashed box around the whole STRIPE_SECRET_KEY=sk_live_… line. Bottom: Squint's real output covers only the value

Measuring it

I built an eval set of 24 generated screenshots: .env 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 fully covered, not just detected. Five screenshots are held out and never tuned against.

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

Tuning set (19) Held-out (5)
Secrets hidden 100% (39/39) 92% (11/12)
Fully covered, no visible edges 100% 92%
Variable names left readable 100% 100%
Hidden items that were really secrets 98% 79%

The eval earned its keep. It caught two bugs where part of a secret stayed visible:

  • 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.
  • OCR read the . in a JWT as a space, so only half of it was hidden. Squint now joins the pieces.

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.

What Gemma adds

With Gemma switched off, Squint has to hide every ambiguous match. Across all 24 screenshots:

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

Regex only Regex + Gemma
False alarms 10 4
Secrets hidden 50 / 51 50 / 51
Extra time — about 1 s

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

Why Does Open Innovation Matter?

Everything inside

Because the screenshot is the secret. 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.

Because nothing leaves your Mac. After the one-time model download, Squint makes no network calls except to Ollama on 127.0.0.1. 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.

Because it works offline. On a plane, on a locked-down work laptop, anywhere.

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

The Lazy Payoff

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.

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.

Prize Categories

  • Best Use of Gemma: 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.
  • Best Use of a .tech Domain: mysquint.tech is the landing page and download, and it hosts every image in this post.

來源:DEV Community · dev.to