This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle.
Disclosure: Paste-Safe and this post were made by AI agents working in the name of MV2 of Munim, Inc. The code was tested before every commit, the numbers below were measured by the project's own test page, and the limits are stated plainly.
What I Built
Paste-Safe is a one-page web app for people who paste client work into ChatGPT, Claude or Gemini and quietly hope nobody minds.
You paste an email, a brief or meeting notes. Paste-Safe swaps the private parts for placeholders:
Before: Hi Maria, Daniel Okafor at Northwind Traders wants the move from
Leeds to Rotterdam explained. Daniel's line is +44 20 7946 0958.
After: Hi [PERSON_1], [PERSON_2] at [ORG_1] wants the move from
[PLACE_1] to [PLACE_2] explained. [PERSON_2]'s line is [PHONE_1].
You paste the masked text into whatever AI you like. When the answer comes back, you paste it into Paste-Safe's third box, and every placeholder turns back into the real name. That works even when the chat model bends the format into [person 2] or a bare PERSON_2, which models do.
What it finds:
- People, organizations and places, found by an open-source NER model running in your browser tab.
- Names the words around them give away: after Mr, Dr or Madame, after Dear or Hi, or under "Kind regards,". These are the names the model misses most.
- Optionally, a second look by Gemma 3 270M, a small open language model that also runs in the tab, for names the first model missed.
- Emails, phone numbers, links, card numbers (Luhn-checked), IBANs, IP addresses and US SSNs, found with regular expressions, which do this better than a model.
- Your own list: client names, project code names, anything you always want hidden. There's also a never-mask list for words the model over-flags.
The same name keeps the same placeholder for the whole session, so a long chat stays consistent. A first name on its own ("Daniel's line") gets the placeholder of the full name it belongs to, so the AI sees one person, not two. The key (placeholder to real name) never leaves the page; you can save it as a JSON file if you want to come back tomorrow.
Who would pay for this? Freelance writers, translators, consultants, recruiters and small agencies: anyone whose client contract has a confidentiality clause written before chat AIs existed. Demand isn't a guess here. Browser extensions in this niche already charge for it: PII Scrubber's Pro tier is $7 a month and PrivacyScrubber's is $15. Paste-Safe's open core is free under the Apache-2.0 licence. The paid side hustle would be what a small business asks for next: a team-shared always-mask list, DOCX and PDF in and out, and an extension that masks right inside the chat box.
Demo
Live: https://munimv2.github.io/paste-safe/
Click Try a sample, then Mask it. The first run downloads the model (about 110 MB) once, and your browser caches it. After that you can switch off Wi-Fi and it still works, which is the easiest way to see that nothing is sent anywhere. The Gemma second look is a checkbox under "What to mask"; it downloads about 323 MB more the first time.
Test page: https://munimv2.github.io/paste-safe/eval.html runs the same masking over 30 labelled messages in your browser and shows every miss.
Code
munimtechnologies
/
paste-safe
Mask client names, emails and numbers before you paste into ChatGPT, Claude or Gemini, then put them back. Open-source NER model running in your browser; nothing uploaded. By MV2 of Munim, Inc.
Paste-Safe
Mask client names before you paste text into a cloud AI, then put them back in the answer.
Try it: https://munimv2.github.io/paste-safe/
Freelancers, agencies and consultants paste client emails, briefs and notes into ChatGPT, Claude or Gemini every day, and many have signed NDAs that never imagined that. Paste-Safe sits in between:
- Paste your text. An open-source name-finding model running in your browser tab marks people, organizations and places. A few fixed rules catch names the words around them give away (after Mr, Dr or Madame, after Dear or Hi, under "Kind regards,"). Regular expressions mark emails, phone numbers, links, card numbers, IBANs, IP addresses and US SSNs. You can add your own always-mask list, such as client names and code names. If you tick it, a small open language model (Gemma 3 270M), also in your tab, takes a second look for names the first model missed.
- Copy the…
How I Built It
The whole app is plain HTML, CSS and ES modules. There's no build step and no server.
The model. dslim/bert-base-NER is a BERT model fine-tuned on CoNLL-2003 to tag people (PER), organizations (ORG), places (LOC) and miscellaneous names (MISC). It's MIT-licensed, and Xenova publishes a quantized ONNX build that Transformers.js runs on WebAssembly:
import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.1';
const ner = await pipeline('token-classification', 'Xenova/bert-base-NER', { dtype: 'q8' });
const tokens = await ner(chunk, { ignore_labels: [] });
The interesting part was turning tokens into spans. The pipeline gives back one label per word piece, not per word, and you can't count on character offsets: She (B-PER), ##eh (I-PER), ##an (O). If you trust that literally, you mask "Sheeh" and leave "an" sitting in the text, where anyone can read the name. core.js handles it in four steps:
-
Align each token to its place in the original text by walking a cursor forward, stripping the
##prefix from word pieces. - Join tagged tokens into spans using the B-/I- tags, gluing word pieces that touch.
- Widen every span to whole-word boundaries, so a half-tagged name is still masked whole.
- Propagate: once "Daniel Okafor" is found, every later "Daniel" or "Okafor" is masked too, even where the model missed it.
BERT also stops reading at 512 tokens, so long text is split at paragraph and sentence boundaries first, and the offsets are carried through.
Overlaps are settled by priority: your own list first, then contact details and numbers, then model names. So daniel.okafor@northwind-traders.example becomes one [EMAIL_1], not an email with a person-shaped hole in it.
Rules for what the model misses. A model trained on English news is weakest exactly where client mail is most personal: "Hi Abdi," or "Kind regards, Linh Tran." So 0.2.0 added three small rules: a capitalised name after a title, after a greeting that ends at a comma, or alone on the line under a sign-off. A short stop list keeps "Dear Team," and "Hi All," unmasked. The rules only ever add people, and there's a checkbox to turn them off.
A second look with Gemma. Rules can't help with "tell james the harbourview contract is signed": there's no title, no greeting, and no capital letter. A language model reads context, so 0.3.0 adds an optional pass with Gemma 3 270M, small enough (323 MB at 4 bits) to run in the same tab:
const gemma = await pipeline('text-generation', 'onnx-community/gemma-3-270m-it-ONNX', { dtype: 'q4' });
const out = await gemma(secondLookMessages(chunk), { max_new_tokens: 128, do_sample: false });
const names = parseNameList(chunk, out[0].generated_text.at(-1).content);
The prompt asks for one name per line as person:, organization: or place:, with one worked example. A 270M model doesn't always follow that, so parseNameList is forgiving about bullets, bold text, numbering and titles. It is strict about one thing: a name is kept only if it really appears in the text, so a made-up or reworded name in Gemma's answer changes nothing. A name Gemma doesn't give a kind for becomes [NAME_1]. Does it help? The test page scores it as its own row, so the answer is in the table below.
Tests, and how the agents worked. The agents that wrote Paste-Safe couldn't open a browser, so everything except the model calls lives in core.js as pure functions, tested with node --test against fake model output: word-piece joining, half-tagged names, chunk offsets, dates that must not be read as phone numbers, [TERM_1] never matching inside [TERM_10], Gemma replies full of bullets and invented names, and a full mask-then-restore round trip. The suite runs before each code commit to the repo, and nothing is committed if a test fails. The real models were checked by a separate agent clicking through the live pages. The first check found one flaw: "Daniel Okafor" became [PERSON_2], but the later "Daniel's" became a separate [PERSON_3], so a chat AI would think there were two people. Version 0.2.1 fixed that: a first or last name on its own takes its full name's placeholder, unless two people in the text share it.
How well it works
Paste-Safe 0.3.1 was run over its test set: 30 made-up requests of the kind people paste into a chat AI, with 97 names of people, organizations and places and 7 contact details and numbers labelled. A name counts as hidden only if every letter of every mention is masked. The always-mask list was left empty, so this measures the model, the patterns and the name rules on their own.
| Setting | Names hidden | Contact details and numbers hidden | Ordinary words masked by mistake |
|---|---|---|---|
| Default (confidence 0.6, name rules on) | 94/97 | 7/7 | 4 |
| Model alone (confidence 0.6, name rules off) | 92/97 | 7/7 | 4 |
| Mask more (confidence 0.4, name rules on) | 95/97 | 7/7 | 5 |
| Default + Gemma 3 270M second look (4-bit) | 97/97 | 7/7 | 13 |
Missed at the default setting: james (#7), harbourview (#7), karen (#7).
Masked by mistake at the default setting: LinkedIn (#9), IBAN (#15), AP (#21), LinkedIn (#23).
What the Gemma second look changed: newly hidden: james (#7), harbourview (#7), karen (#7). Newly masked by mistake: following our call (#1), Draft (#4), Next call (#6), is confirmed for Monday (#8), Turn these notes (#17), volunteers (#20), AP contact (#21), school trip (#25), Our client (#26), 14th (#30). Its prompt was written after the test set, by the same AI agents, so read its row as a best case too.
How one of the misses looked after masking:
7: please make this email shorter. tell james that the harbourview contract is signed and karen will send the keys.
The name rules (a capitalised name after a title such as Mr or Dr, after a greeting such as Dear or Hi, or under a sign-off such as Kind regards) were added after the test set was written, by the same AI agents who wrote it, so read the default row as a best case on these messages, not a promise. The messages themselves were made up by those agents, not taken from real clients. For a name that must never leak, the fix is the always-mask list: add it once and it is masked every time, whatever the model thinks.
Measured in a desktop browser on 2026-10-14. The test page runs in your browser, so anyone can re-run it.
Why Does Open Innovation Matter?
Here it isn't a preference; it's the only design that works.
You can't use a cloud API to find the secrets you don't want to send to a cloud API. A hosted NER endpoint would mean shipping the client's names to a third party in order to hide the client's names from a third party. The detector has to run where the text already is: on your machine, in the tab, with weights you can download and inspect.
Openness buys three more things:
- Verifiability. "We process everything locally" is a claim. An Apache-2.0 repo plus open-weight models plus a network tab you can watch is evidence. For a privacy tool, that difference is the product. The same goes for accuracy: the test set, its labels and the scoring code are all public, so the numbers above can be re-run by anyone instead of taken on trust.
- Zero marginal cost. Every mask runs on the user's CPU. A free tier costs nothing to serve, so the free version can stay genuinely useful instead of being a five-uses-a-day teaser.
- Swappable parts. Each model is one line. A multilingual NER model, a bigger Gemma, or one tuned on legal text can drop in without touching the masking logic, and the test page will say whether it does better. That is how the Gemma second look was added in the first place.
Where the open approach is weaker, honestly: a 2018-era BERT trained on news misses unusual names, lower-case names and non-English text, and a 270M Gemma is a small model with its own misses and false alarms. A large closed model would catch more, but you would have to send it the text, which defeats the point. The always-mask list is the answer for the names that matter most: add a client once and it is masked every time.
Prize Categories
Gemma: Gemma 3 270M runs in the browser tab (Transformers.js, 4-bit ONNX) as Paste-Safe's optional second look for names, and the test page measures what it adds. Gemma is provided under and subject to the Gemma Terms of Use.
Made by MV2 of Munim, Inc., with AI agents working in his name. Paste-Safe lowers the risk of pasting client text into AI tools; it does not make text anonymous and is not legal advice.
Top comments (0)