Finding the duplicate that shares no words.

5 min read·For engineers·Updated Sep 2026

Two people file the same finding six weeks apart. One writes “login endpoint accepts unlimited attempts”, the other “no throttling on the authentication API”. Same problem, zero words in common, and keyword search cannot help you: it is looking for shared text and there isn't any. The connections go unrecorded too. This finding makes that one far more dangerous, and nobody links them, because nobody holds the whole register in their head. Here's how we solve both in one pass at creation time, over findings we've encrypted so thoroughly the database can't read them either.

Your draft finding
Title, systems, and what you wrote
Enrich first
The AI write-up
Your words rewritten to one house format. The keyword index keeps raw text for exact terms; the semantic vector reads the enriched narrative.
Layers 1 + 2 · retrieve, both at once
Keyword index
Exact terms as per-org hashes. Catches the CVE, the port, the hostname.
Meaning index
Encrypted vectors. Catches the same finding written in different words.
Merge, keyword matches first
Shortlist of candidates
Decrypted once, and only now
Layer 3 · judge, concurrently
Is it the same finding?
Sameness of scope and identity, not similarity of wording.
Is it connected to one?
Shared root cause, same asset, one issue amplifying another.
Before you finalize
Duplicate warning, and pre-ticked links
On the review step, each link carrying its reason
Retrieval finds suspects. It never decides. The judgement happens once, on a shortlist, with the text decrypted.
The problem

A register full of near-misses

A duplicate isn't just clutter. It splits the history of one issue across two rows, so remediation work on one is invisible from the other, and your open-findings count is wrong in a way nobody can explain. The fix people reach for, telling everyone to search before filing, fails on exactly the cases that matter: you can't search for a phrasing you didn't think of.

The mirror image of a duplicate is a relation. Take “backups are written to a NAS in the server room, attached to the corporate LAN” and “no policy requirement for offline or immutable copies of recovery media”. One is a technical finding, one is a governance gap, and they share no words at all. Together they are your ransomware recovery plan failing twice over: the copy is reachable from the network that gets encrypted, and nothing in your policy set requires one that isn't. Nobody links them, because the two live in different parts of the register and were probably filed by different people months apart.

So the target is one pass, at creation, that answers two questions. Is this the same finding as something already here? And does it connect to anything already here?

Layer one

Keyword matching, over hashes

The keyword index finds shared terms such as a hostname, CVE or port using per-organisation hashes. It is useful for exact references but cannot reliably connect two descriptions with different vocabulary. The encrypted-search article explains that index and its trade-offs.

Layer two

A second index, for meaning

With semantic matching enabled, we also compare mathematical representations of meaning, called embeddings. That can bring “unlimited login attempts” close to “no authentication throttling” even without shared words. Keyword matching remains useful for exact references.

The two indexes serve different purposes. Keywords preserve identifiers from the original text. Semantic comparison uses the enriched narrative where available, so a short note and a long assessment can be compared in a more consistent form.

Embeddings are encrypted under the organisation's key. Creating them uses our platform-managed Mistral account, independently of the generative provider. See where your AI data goes for processing terms and the BYOK setting.

Layer three

Two agents make the call

Retrieval only produces suspects. The top candidates get decrypted once, then two AI agents run concurrently over that same set, asking different questions:

  • Is this the same finding? Sameness is about scope and identity, not similarity of wording. Two unpatched hosts in the same subnet read almost identically and are two findings.
  • Is it connected to any of them? Shared root cause, same asset, one issue amplifying another, same trust chain, a remediation dependency. The question is whether the relationship helps explain or treat the findings.

Two jobs, two agents, on purpose. Each is tuned for its own question, and each fails alone: a relations failure still leaves you a duplicate verdict, and a duplicate failure still leaves you the candidates to judge by hand.

The output

Directional links, with the reasoning kept

“Related” on its own is nearly useless, so the vocabulary is small and directional. Each link is stored once and read from both ends, so the reverse phrasing is always derived rather than kept as a second row that can drift.

  • Related to / Related to from the other side
    Worth reading together, with no dependency either way
  • Is part of / Contains from the other side
    One finding is a piece of a larger one
  • Worsens / Worsened by from the other side
    One finding makes the other more dangerous or harder to recover from
  • Depends on / Enables from the other side
    Fixing one is a prerequisite for fixing the other

Suggestions arrive on the review step as pre-ticked checkboxes, each with the model's one-line reason for the link. Review each suggestion before saving. Similar language is not enough: check the affected systems, scope and proposed relationship. Clear any link that the evidence does not support. When you accept, the reasoning is saved with the link, so six months later it explains itself instead of sitting there as an unexplained arrow. Bulk imports get the same treatment per row.

For the record, the pair from the top of this article lands as “worsens”: the LAN-attached backup target worsens the missing offline-media policy, and reads as “worsened by” from the policy gap's side.