This is independent security research conducted on my own time. The findings and views expressed here are my own and do not represent my employer.

Every retrieval-augmented system has a vector database somewhere behind it, and almost every one of them stores two things in clear: the embeddings, exactly as the model produced them, and next to each one the document it came from. The database needs the first to search. It usually gets the second because that is what the application wants back.

So anyone who gets a copy of that database gets both. That means a snapshot, a backup in the wrong bucket, a leaked dump, or a database account with more privileges than it should have. The text is simply there. And the embeddings are not the anonymous numbers they look like: Morris and colleagues showed that an inversion model can recover 92% of 32-token inputs exactly from their embeddings alone.

The obvious answer, encrypt everything, breaks the one thing the database is for. An encrypted vector has no useful geometry, so the database cannot rank it. The cryptographic answers that keep search working, homomorphic encryption and secure enclaves among them, exist, and they cost latency, infrastructure or both.

That leaves a narrower question, which is the one my paper with Nirvana Popescu asks in its title: What a Secret Rotation Buys, published this week in Applied Sciences. Take the cheapest transformation that keeps search exact. What does it actually protect, and what does it leak? The answer is more precise than "it is secure" or "it is useless", and the precision is the point. Alongside it, I have released hadamask, an open source library that does this on nine databases.

The trick: rotate, do not encrypt

Similarity search runs on inner products: cosine similarity, dot product and Euclidean distance are all functions of them. An orthogonal matrix Q, a rotation or reflection of the whole space, preserves every inner product:

⟨Qu, Qv⟩ = ⟨u, v⟩

So if every document vector is stored as Q·v, and every query is sent as Q·q, the database computes exactly the scores it would have computed on the clear vectors. It ranks exactly the same documents. It needs no plugin and no configuration change, because from its side nothing is unusual: it receives vectors and searches them. If Q is secret, the stored vectors are no longer in the encoder's coordinate system, and only the application can map queries into theirs.

The construction in the paper is Q = H·D2·P·H·D1: two Walsh-Hadamard transforms, which are fixed and public, interleaved with secret random sign flips and a secret permutation. It runs in O(d log d) and costs about 0.03 to 0.04 ms per vector on the client in the paper's implementation; hadamask's plain numpy version takes about 0.1 ms per query. Its price is structure: it only works on power-of-two dimensions, so 1536-dimensional embeddings are padded to 2048, and every key picks a map from a small, highly structured family.

The alternative is a dense matrix drawn uniformly at random among all rotations, which mathematicians call the Haar measure. It keeps the native dimension and is slower to apply, and it is the case the paper's theory speaks to exactly. hadamask ships both, and the choice matters in practice in a way I come back to below.

The easy part: metadata

The text and metadata do not need to be searched by similarity, so they can be properly encrypted, and in hadamask they are. Every field is encrypted separately with AES-256-GCM under a fresh nonce, with the field name and key id bound in as associated data, and padded so that an amount of 100.00 and one of 9,999,999.99 produce ciphertexts of the same length.

Filters are where the trade-offs are, and they are declared rather than hidden. A field marked blind also gets a keyed equality token, so the database can filter on it and an attacker can see which records share a value. A field marked plaintext stays in clear and supports ranges. Everything else is encrypted and cannot be filtered at all. The library refuses a filter the database cannot evaluate, before anything is sent.

This half is ordinary, well-understood cryptography. It is also, in many deployments, the half that matters most, because the document text is usually more sensitive than any vector derived from it.

What it costs

In the paper, across three BEIR datasets, three encoders and 2,029 queries, exhaustive top-10 results on transformed vectors matched clear search in all nine configurations. On six containerized database engines, five returned identical sets; the sixth differed only within the variability it shows when re-indexing the same clear data.

The library repeats the check on its own terms. Every adapter passes the same contract test against a real server, including a check that only ciphertext reaches the database, and a second test checks that the returned score equals the clear score for cosine, dot product and Euclidean distance on all nine engines.

For cost in a realistic setting, I benchmarked 100,000 DBpedia documents with OpenAI embeddings, 1536-dimensional, against unprotected collections on the same databases with the same index settings:

Qdrant, clearQdrant, hadamaskpgvector, clearpgvector, hadamask
Recall@10 against exact search0.9910.9910.9950.995
Query latency, median3.63 ms4.01 ms5.69 ms6.34 ms
Storage per million vectors13.2 GB13.8 GB17.0 GB17.4 GB

Those numbers use the dense transform, and that is the practical lesson the benchmark taught me. With the Hadamard construction, 1536 dimensions become 2048, which is above pgvector's 2,000-dimension limit for HNSW indexes, so search falls back to an exact scan: 212 ms instead of 6. On Qdrant the padding costs a third more storage. For most real embedding sizes, the dense transform is the right choice (transform="dense"; the library's default is still the paper's Hadamard construction), and the documentation says so. The benchmark is one machine and one run per configuration, and it is reproducible from the repository.

What it leaks

This is the part the paper exists for, and it is the part I would read first if I were deciding whether to use this.

A rotation hides the coordinate system. It does not hide the geometry. Every pairwise similarity in the corpus is preserved, because that is what makes search work, and therefore every pairwise similarity is visible to whoever holds the snapshot: which documents are near-duplicates, how they cluster, the whole nearest-neighbour graph. For the Haar-uniform case, the paper shows that this matrix of similarities, the Gram matrix, characterizes exactly what a snapshot of a corpus under one key reveals. For the structured Hadamard construction, the Gram matrix is only a lower bound on what leaks, and the construction has no established confidentiality guarantee beyond the attacks we tested.

Then we attacked it.

Known pairs recover the map. An attacker who knows enough matched pairs of clear and transformed vectors, in general as many as the dimension, recovers the rotation exactly, and with it the clear vectors for everything else. Rotation offers no protection against an adversary who can see documents go in and vectors come out.

Public data supports topic inference. With no matched pairs at all, only the protected vectors and public embeddings from the same distribution, an attacker reached median four-way topic accuracies of 0.833, 0.808 and 0.869 for the three encoders in the primary experiment. A paired evaluation with distribution shift lowered that accuracy, but label reassignment and normalized mutual information still showed residual topic structure.

Noise is not a settled answer. Noise padding, which gives up exactness, is the obvious next defense. Our findings on it are limited to four estimators on one dataset, one encoder and one corpus size. I would not draw general conclusions from them, and the paper does not.

The paper's last sentence is the honest summary: these results characterize exact similarity preservation and measured leakage, not encryption or general semantic confidentiality. hadamask's README says the same thing in a box at the top. The vector transform is obfuscation with measured leakage. The metadata encryption is encryption.

So what does a rotation buy?

It buys a specific change in what a stolen copy is worth.

Without it, a snapshot is a corpus: the documents in clear, and vectors in the encoder's own coordinates, ready for an off-the-shelf inversion model trained on that coordinate system. With it, the documents are AES-GCM ciphertexts, and the vectors are a geometry with no coordinates attached. The attacker can still learn which documents resemble which, and with auxiliary data from the same distribution, roughly what they are about. To learn more, they need known pairs, or an attack we did not test. The bar moves from reading the dump to mounting an inference attack, with auxiliary data, against a geometry-only view.

That is worth having where the threat is data at rest in a database you do not fully control: a managed vector database, a shared cluster, backups that travel. It is not worth much, or anything, against an attacker who controls the application or the key, one who can insert chosen documents and later read a snapshot, or one who watches queries and access patterns. And it is the wrong tool if the corpus geometry itself is the secret. If knowing that two documents are similar is already the damage, you need a cryptographic protocol or trusted hardware, not a rotation.

There is also an operational boundary that is easy to miss. If the database is configured to compute embeddings itself from text, as several engines offer, then it receives clear text and nothing on the client can help. hadamask's Weaviate adapter refuses such collections for exactly that reason.

Why a library, and not only a paper

A measurement of leakage is only useful if someone can act on it, and acting on this one means deploying a transform consistently across every write and every query, managing a key, encrypting metadata without breaking filters, and doing all of it against whichever database the system already uses. Getting any of those wrong quietly removes the protection or quietly breaks search.

So hadamask is a Python library, not a proxy or a service, with one API over Qdrant, Weaviate, Milvus, Chroma, pgvector, Elasticsearch, OpenSearch, Redis and MongoDB Atlas, wrappers for LangChain and LlamaIndex, and envelope encryption of the master key through HashiCorp Vault, AWS KMS, Google Cloud KMS or Azure Key Vault. The SDK wrappers for Qdrant and Weaviate raise an error on any method that would write or read data without protection, rather than passing it through.

pip install "hadamask[qdrant]"

It is alpha software and has not had an external cryptographic audit. If you find a problem, please email me rather than opening a public issue.

The pattern

Across this blog, the recurring argument has been that a system should establish deterministically whatever it can, and leave to inference only what it must. Here the same idea applies to protection. The metadata can be encrypted, so it is. The vectors cannot be encrypted without losing search, so they get the strongest transformation that keeps search exact, and then a careful statement of what is left exposed.

That statement is the useful part. "Encrypted vector database" is a phrase that should make a security engineer ask what exactly is encrypted. For this one the answer is written down, measured, and in a peer-reviewed paper: the text, yes; the coordinates, hidden; the geometry, visible, and here is what an attacker can do with it.