<?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: karthik valluri</title>
    <description>The latest articles on DEV Community by karthik valluri (@karthik_valluri_832bd99be).</description>
    <link>https://dev.to/karthik_valluri_832bd99be</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%2F1718673%2F4bf0bca4-9141-44ff-9838-94e3283d1f6e.jpg</url>
      <title>DEV Community: karthik valluri</title>
      <link>https://dev.to/karthik_valluri_832bd99be</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/karthik_valluri_832bd99be"/>
    <language>en</language>
    <item>
      <title>I ran two OCR engines and a BERT model entirely in the browser — here's what I learned</title>
      <dc:creator>karthik valluri</dc:creator>
      <pubDate>Sat, 07 Nov 2026 05:24:04 +0000</pubDate>
      <link>https://dev.to/karthik_valluri_832bd99be/i-ran-two-ocr-engines-and-a-bert-model-entirely-in-the-browser-heres-what-i-learned-31m</link>
      <guid>https://dev.to/karthik_valluri_832bd99be/i-ran-two-ocr-engines-and-a-bert-model-entirely-in-the-browser-heres-what-i-learned-31m</guid>
      <description>&lt;p&gt;Every day, someone pastes a dashboard screenshot into Slack, a Jira ticket, or a slide deck with customer names, emails, and revenue figures still perfectly readable. The usual fix is a redaction tool — and almost every redaction tool uploads your screenshot to a server, which is exactly the thing you were trying to avoid.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://vaultsnip.pages.dev" rel="noopener noreferrer"&gt;VaultSnip&lt;/a&gt;: a free, open-source tool that finds and blurs sensitive text in screenshots. The constraint I set myself: &lt;strong&gt;everything runs on your device.&lt;/strong&gt; No backend, no account, no upload. The whole app is static files on Cloudflare Pages; your screenshot never leaves the browser tab.&lt;/p&gt;

&lt;p&gt;That constraint forced me to run a full ML pipeline client-side. Here's what that actually took.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The pipeline: two OCR engines and a PII model, all in the tab
&lt;/h2&gt;

&lt;p&gt;To decide what to blur, you first need to know what text is in the image and where. VaultSnip layers three engines, each running in a Web Worker:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tesseract.js&lt;/strong&gt; does the fast first pass. Masks land in a few seconds, so the user sees results immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PP-OCRv5&lt;/strong&gt; (PaddleOCR's mobile detection + recognition models, as ONNX) catches what Tesseract misses — small print, low-contrast labels, text inside chart chrome. I run the official ONNX exports through ONNX Runtime Web and wrote the DB post-processing and CTC decoding myself in TypeScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A BERT-small PII model&lt;/strong&gt; (int8-quantized ONNX, via Transformers.js) classifies the recognized text: is this a person's name, an email, an ID, a company? That decides what gets masked versus left alone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A design rule that mattered more than any model choice: &lt;strong&gt;later passes only ever add masks, never remove them.&lt;/strong&gt; The user watches the screenshot get progressively safer, and an engine disagreement can't un-redact something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: cross-origin isolation is the real price of admission
&lt;/h2&gt;

&lt;p&gt;ONNX Runtime Web's threaded SIMD build needs &lt;code&gt;SharedArrayBuffer&lt;/code&gt;, and browsers only give you that when the page is cross-origin isolated — &lt;code&gt;Cross-Origin-Opener-Policy: same-origin&lt;/code&gt; and &lt;code&gt;Cross-Origin-Embedder-Policy: require-corp&lt;/code&gt; headers on everything.&lt;/p&gt;

&lt;p&gt;That sounds like two lines of config. In practice it means &lt;em&gt;every&lt;/em&gt; resource you load — every script, every wasm file, every model weight — must either be same-origin or explicitly opt in. It shaped the entire architecture: all models are bundled and served from the same origin, nothing is pulled from a CDN at runtime.&lt;/p&gt;

&lt;p&gt;Which turned out to be a feature. "No requests to third-party servers" stopped being a promise and became something you can verify in the Network tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: model size is a UX problem, not a bandwidth problem
&lt;/h2&gt;

&lt;p&gt;The int8-quantized BERT is small by ML standards, and the PP-OCRv5 mobile models are a few megabytes each, gzipped. Users will wait for that &lt;em&gt;once&lt;/em&gt; — if the app is honest about it and useful before the download finishes.&lt;/p&gt;

&lt;p&gt;That's why the layering matters: Tesseract.js (which is comparatively tiny) delivers first masks in seconds while the heavier models are still warming up. The slow part runs while the user is already getting value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: measure what you miss, and publish it
&lt;/h2&gt;

&lt;p&gt;Any detection pipeline misses things. Most tools in this space deal with that by never mentioning it.&lt;/p&gt;

&lt;p&gt;I went the other way: the repo has an automated benchmark that renders fictional dashboards, runs the full pipeline, and measures exactly how much readable text area survives unmasked. Right now that's &lt;strong&gt;about 0.25% of readable area&lt;/strong&gt;, and the number is published in the repo's ACCURACY.md, failure cases included.&lt;/p&gt;

&lt;p&gt;This felt risky ("my tool's README admits it's imperfect!") and has been nothing but positive. Users trust a published ~ 0.2% far more than an implied 0%, and the benchmark doubles as a regression gate in CI — no change ships if the number gets worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 4: the replica — editing pixels in place
&lt;/h2&gt;

&lt;p&gt;Blurring is fine for chat, but sometimes you need a screenshot that still &lt;em&gt;looks real&lt;/em&gt; — for a demo, a tutorial, a portfolio. VaultSnip's "replica" mode replaces each sensitive value, in place, with a synthetic one: same position, same font size, same color and alignment, with plausible fake data (via Faker, also running locally).&lt;/p&gt;

&lt;p&gt;The hard rule, enforced by a pixel-diff e2e test: &lt;strong&gt;zero changed pixels outside the replaced text boxes.&lt;/strong&gt; No redrawing charts, no reconstructed layouts. A bar-chart edge that runs through a text box survives the replacement. The replica &lt;em&gt;is&lt;/em&gt; the original image, surgically edited — which is the only way it stays believable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it became
&lt;/h2&gt;

&lt;p&gt;What started as a web page is now also a Chrome/Edge extension (same pipeline, plus one-key blurring of charts on live pages) and a Windows desktop app (Tauri 2, with a global hotkey that snips any screen region straight into the masker). All of it Apache-2.0, all of it offline-capable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Try it (no sign-up):&lt;/strong&gt; &lt;a href="https://vaultsnip.pages.dev" rel="noopener noreferrer"&gt;https://vaultsnip.pages.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/karthikvalluri85/vaultsnip" rel="noopener noreferrer"&gt;https://github.com/karthikvalluri85/vaultsnip&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&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/karthikvalluri85" rel="noopener noreferrer"&gt;
        karthikvalluri85
      &lt;/a&gt; / &lt;a href="https://github.com/karthikvalluri85/vaultsnip" rel="noopener noreferrer"&gt;
        vaultsnip
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Turn sensitive report snips into client-safe replicas
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a href="https://repoclip.io/v/c8e1ca5e-1926-42cf-989d-bf07515d310f" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a725aab38c3619ceccef398249075e30daee10730a1ab685b9fecac33f0d2c23/68747470733a2f2f7265706f636c69702e696f2f6170692f62616467652f63386531636135652d313932362d343263662d393839642d626630373531356433313066" alt="VaultSnip: Secure Your Data, Share with Confidence"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;VaultSnip&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/karthikvalluri85/vaultsnip/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/karthikvalluri85/vaultsnip/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://vaultsnip.pages.dev" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3b2faefbdb8262dc025e99815e3cccd0536c34300064e7f997683f90697c3106/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6976652d7661756c74736e69702e70616765732e6465762d306436623564" alt="Live app"&gt;&lt;/a&gt;
&lt;a href="https://github.com/karthikvalluri85/vaultsnip/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/39a434c39c97856247fc55ebc90e8cc1cb9871558a37bf1bf83cbaca3be89d69/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c7565" alt="License: Apache 2.0"&gt;&lt;/a&gt;
&lt;a href="https://github.com/karthikvalluri85/vaultsnip/CHANGELOG.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/40d8ef7bf04351847b5091db7741e64e9cfdd4c21131543d9b3bae5229df929f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f7061636b6167652d6a736f6e2f762f6b61727468696b76616c6c75726938352f7661756c74736e6970" alt="Version"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Blur sensitive text in any screenshot. Free, offline, in your browser.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Paste a screenshot of a dashboard, report, table or app. VaultSnip finds names, numbers, emails and IDs and masks them
&lt;strong&gt;on your device&lt;/strong&gt;, shows the first masks within seconds, keeps checking in the background, and gives you a masked
image you can copy straight into Slack, Teams, a ticket or a slide. Nothing is uploaded and nothing is stored.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://vaultsnip.pages.dev" rel="nofollow noopener noreferrer"&gt;https://vaultsnip.pages.dev&lt;/a&gt; (no sign-up, no key, no upload) ·
&lt;a href="https://chromewebstore.google.com/detail/cdmldcadgijefheekeamiicgcoimlbkg" rel="nofollow noopener noreferrer"&gt;Chrome&lt;/a&gt; ·
&lt;a href="https://microsoftedge.microsoft.com/addons/detail/jhlhonheebmedikjjmeaklgfeohabloa" rel="nofollow noopener noreferrer"&gt;Edge&lt;/a&gt; ·
&lt;a href="https://huggingface.co/spaces/karthikvalluri/vaultsnip" rel="nofollow noopener noreferrer"&gt;Hugging Face&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Works with screenshots of Power BI, Tableau, Looker, Qlik, Excel, Google Sheets, Salesforce, Jira, PDF report pages and
any other app on your screen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On the desktop:&lt;/strong&gt; a &lt;a href="https://github.com/karthikvalluri85/vaultsnip/desktop/README.md" rel="noopener noreferrer"&gt;small app&lt;/a&gt; with a hotkey. Press Ctrl+Alt+V (Control+Option+V on a Mac), drag over the
part of the screen you want, and once the check passes the masked image is on your clipboard. Installers are…&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/karthikvalluri85/vaultsnip" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you've run models in the browser — or hit a wall trying — I'd genuinely like to hear how it went. And if you can break the masking with a screenshot it misses, file an issue; the benchmark suite grows from exactly those.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
