Squint 在 Mac 上自動檢查並遮蔽截圖中的敏感資料
Squint: your screenshots have secrets.
作者開發的 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.
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:
- Copy a screenshot of a
.envfile with ⌃⇧⌘4. - About 1.5 seconds later, the banner appears.
- 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
- Open
Squint.dmgand drag Squint into Applications. - Open Squint. The app is not notarized yet, so the first time macOS blocks it: open System Settings → Privacy & Security and click Open Anyway.
- 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).
- 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
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.
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.
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}.
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
rawmode 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.
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.
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.
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.
| 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:
| 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?
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











