Skip to main content

Testing Browser-Based Encryption in a Rails App

Learn how Inpages.me tests browser-based encryption across Rails requests, locked states, and client transitions without relying on server decryption.

A browser-encryption design can look convincing in a diagram and still fail at the seams: a locked vault, a Turbo visit, an attachment transition, or a controller that accepts the wrong payload. The test strategy must prove the boundary without adding a server-side shortcut that defeats it.

Turn a security statement into testable behavior

“Encrypted in the browser” is too broad for a test. Inpages.me breaks it into observable claims: the browser holds the usable key, protected fields are transformed before submission, Rails stores ciphertext, protected media is opaque to Active Storage analysis, and an encrypted Memory cannot be published.

Each claim belongs in the browser encryption architecture and the threat model, not in an unqualified badge.

Exercise the browser cryptography path

JavaScript tests and browser tests cover vault setup, local unlock, AES-GCM transformation, and the locked fallback. The aim is to run the same browser primitives used by the feature, rather than reproducing encryption in Ruby only because it is easier to assert.

Fixtures are fictional and minimal. A test does not need a real journal to demonstrate that the UI shows a locked placeholder until a vault unlock event occurs.

The same principle applies to browser-side search over protected journal content: tests must show that the query is matched only after local decryption, not by a Rails shortcut.

Verify the request and storage boundary

Request and service tests assert ownership, required payload shape, and ciphertext persistence. They also protect against a regression where a controller silently accepts plaintext during an encrypted transition or attempts server-side decryption.

For encrypted files, the checks include opaque application/octet-stream blobs and the absence of server-side media analysis. See encrypting files before Active Storage sees them for the storage path.

Cover an all-or-nothing protection transition

A persisted standard Memory has text, links, attachments, and sometimes inline images. Tests cover successful replacement, version conflicts, malformed replacement references, rollback, and cleanup of an abandoned staged blob. The state must remain coherent even though browser uploads and database commits are separate operations.

The integrity model is described in the atomic Memory encryption transition.

Treat a locked vault as a normal state

Tests should assert what is unavailable before unlock, not just what appears after it. Inpages.me keeps encrypted fields read-only while locked and does not silently downgrade protection to make a save succeed. That distinction prevents an availability workaround from weakening the boundary.

The browser must also recover correctly after Turbo navigation. This is one reason the CSP and nonce behavior in the Turbo CSP note is tested as part of the environment around the editor.

Make failures explain the boundary they protect

A good regression name states the meaningful failure: “does not publish an encrypted Memory,” “does not attach an unowned replacement blob,” or “does not decrypt on the server.” That gives future maintainers a reason not to simplify away an apparently inconvenient check.

Tests can increase confidence, but they cannot prove safety against a malicious extension or compromised device. The known limits remain part of the release decision.