I built a "Zero-Trust" React Input library. Then real users installed it.
Building a security library is high-stakes. I spent months building FieldShield — a React library that prevents session recorders, browser extensions, and AI screen readers from capturing sensitive form inputs by isolating the real value in a Web Worker thread.
The concept is simple: The DOM always contains scrambled characters (xxxxx). The "real" value only leaves the worker when your submit handler explicitly requests it.
The architecture worked perfectly during development. Then real users installed it.
Here are the six bugs that survived every test I wrote — and only appeared when developers used the library in their own environments.
The Tech: How FieldShield works in 30 seconds
The real input always contains xxxxxxxx. Session recorders read input.value and get nothing. AI screen readers like Microsoft Copilot Vision or Google Gemini read the DOM and get nothing.
According to reports this week, 99% of enterprises run at least one browser extension — and any extension with DOM access can read input.value directly. FieldShield eliminates that attack surface entirely. The real value lives exclusively in a Web Worker thread.
Sounds simple. Building it correctly was a nightmare.
1. The worker file that didn't exist for anyone else
Severity: Critical
The library worked flawlessly on my machine. The first consumer who installed it reported a blank input.
The root cause: The worker was instantiated using an absolute path that resolved correctly inside my Vite development repo. When a consumer installs it from npm, that file lives in node_modules. Their dev server had no idea how to serve that absolute path.
The Fix: I had to inline the worker as a blob URL at build time. The worker source is now compiled into the bundle. No file serving dependency.
The "Claude Code" Connection: This is the same class of mistake that caused the Anthropic Claude Code source exposure in March 2026. Packaging mistakes are high-stakes. Always run npm pack --dry-run before every publish.
2. The monospace font that hid everything
Severity: High
FieldShield stacks two layers: a mask layer showing the "masked" output, and a real (transparent) input underneath. The cursor lives in the real input. For this to work, the cursor position must align pixel-perfectly with the mask layer.
During development, I used IBM Plex Mono (monospace). Every character has the same width. The x scramble characters were exactly as wide as the characters the user typed.
The Bug: The first consumer used Inter (proportional). In Inter, x is narrower than W. After 30 characters, the cursor drifted visibly away from the text.
The Fix: Explicit CSS property resets on six inheritable properties: text-align, text-indent, text-transform, font-variant-ligatures, font-kerning, and hyphens.
3. The "Ghost" Placeholder
Severity: Medium
A consumer reported "blurred" placeholder text. It looked like a ghost image.
FieldShield renders placeholders in two places: a span in the mask layer and the native placeholder on the hidden input. I thought color: transparent would hide the native one.
The Bug: Browser implementations apply their own opacity reduction on top of currentColor. Once a consumer's font differed from my dev font, the native placeholder rendered at a slightly different position than my mask layer, producing a "blur."
The Fix: I added a specific CSS rule setting color: transparent specifically on the ::placeholder pseudo-element of the real input.
4. The CSS property that came from 3 levels up
Severity: Medium
A consumer’s placeholder text was center-aligned when it should have been left-aligned.
The Bug: Their application had text-align: center on the #root element (the Vite default). Because text-align is an inherited property, it cascaded into my library’s mask layer. The visible text shifted, but the cursor (anchored to the input) stayed left.
The Fix: I had to explicitly reset all six inheritable CSS properties on the internal mask layer. This is a novel finding for overlay-based security components that isn't documented in current CSS isolation literature.
5. The CSS import that Vite 4+ refused
Severity: Medium
The README told consumers to import the stylesheet via the dist path. This failed in Vite 4+.
The Bug: Vite 4+ strictly enforces the exports field in package.json. If a path isn't explicitly listed, Vite blocks the import — even if the file exists.
The Fix: Added explicit CSS exports to package.json:
JSON
"exports": {
"./style.css": "./dist/style.css"
}
6. Ctrl+Z reveals the "Scrambled Truth"
Severity: Low (Architectural)
A consumer noticed that pressing Ctrl+Z shows xxxx characters instead of the previous real value.
The Reality: This isn't a bug; it's a security guarantee. The browser’s native undo history tracks changes to the DOM. Since the DOM only ever sees xxxx, that’s all it can "undo" to. The real value is isolated in the Worker memory, which the browser's undo mechanism cannot access by design.
The Future (v1.2): I am building a custom undo stack that intercepts Ctrl+Z and restores the value from the Worker memory.
The strongest test matrix isn't unit tests
None of these appeared in my test suite. They were discovered by real environments: a new CRA project, a Webpack project, a Next.js project — each with their own default fonts and CSS resets.
If you build healthcare or fintech forms in React — or any form that collects sensitive data — FieldShield is now live.
Install: npm install fieldshield
Live Demo: fieldshield-demo.vercel.app
GitHub: github.com/anuragnedunuri/fieldshield
Feedback welcome — especially if you find Bug #7!
Top comments (0)