4 Checks Isolating Client-Side Random Password Generators

Silent vs Chatty: Two Password Generators, One Critical Difference

Last month, I audited two client-side password generators for a healthcare onboarding portal. Both looked identical—clean UI, slider for length, toggle for special characters, copy-to-clipboard button. Generator A shipped every generated password to a third-party analytics endpoint inside a fetch() call buried in a minified bundle. Generator B never touched the network after initial page load. Same UX. Radically different privacy posture. The difference between these two is the entire reason I'm writing this walkthrough.

If you're building a random password generator for a compliance-sensitive environment—HIPAA-covered healthcare apps, PCI-DSS-scoped fintech flows, or any system where the password itself is regulated data—the "never touches the network" property isn't a nice-to-have. It's the core deliverable. Here's how I build one, end to end, with proof at every step.

Threat Model: "Probably Fine" vs "Provably Silent"

The Naive Assumption

Most teams assume that if the password generation logic runs in the browser, the password stays in the browser. That assumption collapses the moment you introduce a third-party script. I've reviewed generators—live in production—that load Google Analytics, Sentry SDKs, and even ad pixels on the same page where passwords are generated. Each of those scripts has full DOM access. Each can read the password field before the user copies it. Each runs in the same JavaScript execution context as your generation logic.

The Rigorous Model

I define zero data transmission as a hard, testable property: after the initial HTML, CSS, and JavaScript bundle loads, the page makes zero outbound network requests. No XHR. No fetch. No WebSocket. No beacon. No image pixel loads. No DNS prefetch. No navigator.sendBeacon(). The password is generated, displayed, and copied to clipboard entirely within the browser's memory space, and no byte of entropy or generated output ever leaves the device.

This is the bar. Everything else in this workflow exists to hit it.

Entropy Source: Math.random() vs Crypto.getRandomValues()

The Wrong Tool

Math.random() is a pseudo-random number generator seeded by the browser's V8 engine. It's fast, deterministic, and completely unsuitable for cryptographic purposes. Its internal state can be reconstructed if an attacker observes enough consecutive outputs. I've reviewed generators—still live on the web—that use Math.random() to pick character indices from an alphabet string. That's not a password generator. That's a vulnerability with a UI wrapper.

The Right Tool

window.crypto.getRandomValues() pulls from the operating system's cryptographically secure random number generator (CSPRNG). On Linux, that's /dev/urandom. On Windows, it's BCryptGenRandom. On macOS, it's SecRandomCopyBytes. The browser doesn't implement its own RNG here—it delegates to the kernel, which collects entropy from hardware interrupt timing, disk seek patterns, and thermal noise.

Here's the concrete math. If I'm generating a 16-character password from a 94-character printable ASCII set (lowercase, uppercase, digits, and 32 symbols), each character contributes log₂(94) ≈ 6.55 bits of entropy. A 16-character password yields 16 × 6.55 ≈ 104.8 bits. That falls short of the NIST SP 800-63B recommendation of 112 bits for high-value accounts. Bump to 18 characters and you get 117.9 bits—comfortably above the threshold. I use getRandomValues() to fill a Uint8Array, then map each byte modulo the character set length, applying rejection sampling to eliminate modulo bias on the remainder.

The result is a uniform distribution across all 94 characters, with no modulo bias skewing selection probability toward lower-indexed symbols. Without rejection sampling, bytes 0 through 187 map cleanly in two full cycles over the 94-character set, but values 188 through 255 wrap around and give the first 68 characters a slightly higher probability of being selected. That bias is small—roughly 2.7% per affected character—but it is measurable, it is unnecessary, and in a compliance audit, "small but measurable bias" is the kind of finding that gets written up in a report. Rejection sampling discards any byte value ≥ 188 and requests a fresh random byte instead. The performance cost is negligible: about 26% of bytes get rejected on average, and getRandomValues() is fast enough that the user never perceives a delay.

That is check one. Three more complete the isolation perimeter, and each one closes a distinct exfiltration channel that a "chatty" generator leaves wide open.

Check 2: Network Silence—Proving Zero Outbound Requests

The threat model demands that after the initial page load, the generator makes zero network requests. Proving this requires more than a visual scan of the source code. Minified bundles hide fetch() calls. Dynamic imports load scripts after user interaction. WebRTC connections can open STUN/TURN channels that bypass traditional network monitoring. A rigorous verification process layers three techniques.

DevTools Network Tab—Manual Baseline

Open the page in Chrome DevTools, navigate to the Network tab, check "Disable cache," and filter by "All." Reload the page. Note every request—HTML, CSS, JavaScript, fonts, images. Once the page is fully loaded and idle, generate a password. Toggle every option. Copy to clipboard. Generate 50 more passwords in rapid succession. Watch the network panel. If a single new request appears, the generator fails this check. I have caught analytics SDKs that fire events on clipboard writes, telemetry endpoints that log password_generated events with length and character-set metadata, and even a generator that sent the full password to a logging endpoint disguised as an error report.

Content Security Policy—Enforced Perimeter

A manual DevTools check proves silence at a point in time. A Content Security Policy enforces it permanently. Set connect-src 'self' or, for maximum isolation, connect-src 'none' in the response headers serving the generator page. This blocks fetch(), XMLHttpRequest, WebSocket, EventSource, and navigator.sendBeacon() from reaching any destination not explicitly whitelisted. Any attempt to transmit data triggers a CSP violation, which the browser reports to the configured endpoint. In production, I deploy with Content-Security-Policy-Report-Only for one week to catch violations without breaking functionality, then switch to enforcing mode.

Service Worker Interception—Programmatic Verification

For automated testing, I register a Service Worker that intercepts every fetch event via the FetchEvent API. The worker logs each request URL, method, and timestamp to an in-memory array. After running the generator through its full interaction cycle—generate, toggle options, copy, clear—the test asserts that the array contains zero entries post-initial-load. This runs in CI alongside the unit test suite, so any future commit that introduces a network call fails the build immediately.

Check 3: Script Isolation—No Third-Party Code in the Execution Context

Even with zero network requests, a third-party script loaded during initial page load can exfiltrate the password through other channels. A script with DOM access can read the password input field, attach MutationObserver instances to detect when a new password appears, or override navigator.clipboard.writeText() to intercept the copied value before it reaches the system clipboard. The script does not need to send the data over the network during the session—it can store it in localStorage, IndexedDB, or a Service Worker cache for later transmission.

The Script Inventory Audit

I maintain a complete inventory of every <script> tag, every dynamically imported module, and every eval or Function constructor call in the bundle. For a compliance-scoped generator, the inventory should contain exactly one entry: the application's own JavaScript. No analytics. No error tracking. No A/B testing SDKs. No font loaders. No cookie consent banners. If the business requires analytics on the page, they run on a different page—not this one.

Sandboxed iframe Isolation

When business constraints absolutely require third-party scripts on the same page, I isolate the generator inside a sandboxed iframe with the sandbox="allow-scripts" attribute and no allow-same-origin. This gives the iframe a unique origin, preventing parent-page scripts from accessing the iframe's DOM. The parent page communicates with the iframe only through postMessage, and the messages contain user actions (e.g., "generate," "copy")—never the password itself. The password lives and dies inside the iframe's memory space.

Subresource Integrity for First-Party Scripts

For the application's own scripts, I apply Subresource Integrity (SRI) hashes. If the script content is tampered with or altered in transit, the browser refuses to execute it. This protects against CDN compromise or man-in-the-middle attacks that could inject exfiltration code into a first-party bundle that otherwise passed every other check.

Check 4: Clipboard and Memory Hygiene—The Last Mile

The password is generated, the network is silent, no foreign scripts are watching. The final exfiltration vector is the application's own handling of the password after generation—how it is displayed, stored, copied, and eventually discarded.

Clipboard Without Side Effects

Use navigator.clipboard.writeText() for copy operations, not the deprecated document.execCommand('copy'). The Clipboard API is asynchronous, returns a Promise, and does not require a hidden text area or temporary DOM element that could be observed by a MutationObserver. Verify that the clipboard write does not trigger secondary events—some browsers fire a copy event on the document, which third-party scripts can listen for. In the generator's event handler, call event.preventDefault() and event.stopPropagation() to ensure no listener downstream receives the clipboard payload.

Auto-Clear and Memory Erasure

After a password is copied, start a 30-second timer. When it elapses, clear the displayed password from the DOM and overwrite the variable holding it in memory. JavaScript does not provide true memory erasure—garbage collection is non-deterministic—but setting the variable to null and triggering a new generation reduces the window during which the password exists in readable form. Never write the password to localStorage, sessionStorage, IndexedDB, or any persistent store. Never log it to the console. Never include it in error reports or stack traces.

Display Masking

Show the generated password in a read-only input field with type="text" so the user can verify it, but provide a toggle to mask it with type="password" for shoulder-surfing protection. The field should have autocomplete="off" and spellcheck="false" to prevent the browser from saving it in form history or running spellcheck analysis on the generated string.

The Bottom Line

A client-side password generator that touches the network is not a password generator. It is a data collection tool that happens to produce strings. The four checks I have outlined—entropy source, network silence, script isolation, and clipboard hygiene—form a verification framework that proves a generator is what it claims to be. Each check closes a specific exfiltration channel: weak randomness, outbound requests, third-party DOM access, and post-generation leakage. Together, they establish the property that matters most in a compliance-sensitive environment: the password never leaves the browser, and you can prove it.

Generator A from my healthcare audit failed checks two and three. It shipped passwords to a third-party analytics endpoint and loaded a Sentry SDK with full DOM access on the generation page. Generator B passed all four. Same UX. Radically different privacy posture. The difference was not accidental—someone on Generator B's team had done this work. Someone had run these checks. That is the difference between "probably fine" and "provably silent," and in a regulated environment, that difference is the entire deliverable.

Frequently Asked Questions

Are online password generators safe to use?

Online password generators are safe if they operate entirely on the client-side, meaning the generation happens in your browser rather than on a remote server. You should always verify that the tool explicitly guarantees zero data transmission to ensure your passwords are never stored or intercepted.

What is a client-side password generator?

A client-side password generator uses your device's local resources, like JavaScript and the Web Crypto API, to create random passwords without sending any information over the internet. This approach ensures maximum data privacy because the generated passwords never leave your computer.

Do password generators send my passwords to a server?

Reputable password generators do not send your passwords to a server, but it is crucial to check the website's privacy policy and code implementation. Tools designed with strict zero data transmission policies ensure that your sensitive credentials remain completely isolated from external networks.

How can I verify if a password generator is truly offline?

You can verify a generator's offline status by turning off your internet connection after loading the page and testing if it still creates passwords. Additionally, using your browser's developer tools to monitor network activity will confirm that no data is being transmitted during the generation process.

Can a website steal a password generated on their own page?

While a malicious website could theoretically write code to capture generated passwords, trusted open-source generators are transparent and do not include such tracking. Using well-known tools that explicitly enforce zero data transmission guarantees that your generated strings are not secretly sent to their servers.

What does zero-knowledge password generation mean?

Zero-knowledge password generation means the application creates random strings without ever knowing, storing, or transmitting the final output. This cryptographic principle ensures that even the creators of the tool cannot access the passwords you generate on your device.

Are JavaScript password generators secure?

JavaScript password generators are highly secure when they utilize the Web Crypto API, which provides cryptographically strong random numbers directly from the browser. As long as the script is configured for zero data transmission, it is an excellent method for safely generating passwords offline.

Is it safer to generate passwords locally or online?

Generating passwords locally is significantly safer because it eliminates the risk of interception during network transmission. When a generator operates completely offline on the client-side, you maintain total control over your data and achieve maximum privacy.

Do browser extensions steal generated passwords?

Not all browser extensions steal passwords, but you should only install extensions from reputable providers that explicitly state they do not require network permissions. Reviewing the extension's permissions and ensuring it operates with zero data transmission will protect your generated credentials from being leaked.

Why is zero data transmission important for password privacy?

Zero data transmission is critical because any data sent over the internet can potentially be intercepted, logged, or compromised by third parties. By keeping the entire random number generation process strictly on the client-side, you guarantee that your passwords are never exposed to external vulnerabilities.