Steganographic exfiltration
An attacker with write access encodes secrets into vectors via rotation, scaling, or noise injection. The data leaves disguised as ordinary embeddings.
Vector databases are the soft underbelly of the AI stack — models trust them, agents query them, audits don't ask about them. VectorPin binds every embedding to its source text and producing model with an Ed25519 signature. Steganographic exfiltration, silent rotation, ingestion poisoning — all break the pin the moment a single float is touched.
$ vectorpin audit-lancedb ✓ 18,442 pinned ✓ 18,440 ok ✕ 2 VECTOR_TAMPERED └ exit 1
The companion VectorSmuggle research project demonstrates each of these in the wild. Every technique requires modifying the vector after the model produces it. Cryptographic pinning is the kill shot.
An attacker with write access encodes secrets into vectors via rotation, scaling, or noise injection. The data leaves disguised as ordinary embeddings.
A compromised pipeline injects tampered vectors into the store. Downstream RAG retrieves them and the agent quotes the attacker's words.
The embedding model gets swapped in production. Old vectors and new vectors share a database. Nothing logs the cutover. Search quality silently collapses.
Below is a 32-cell view of an embedding pinned at ingest time. Drag any cell up or down to perturb a dimension — the cryptographic stamp flips the moment the vector hash changes.
> verifier.verify(pin, vec) outcome: OK signed_at:2026-05-07 by: prod-2026-05
"Every steganographic technique requires modifying the vector after the model produces it." — from the spec.
Python, Rust, and TypeScript are byte-for-byte compatible. A pin produced by any of them verifies on the other two — locked together by shared test vectors in CI.
from vectorpin import Signer, Verifier
signer = Signer.generate(key_id="prod-2026-05")
embedding = my_model.embed("The quick brown fox.")
pin = signer.pin(
source="The quick brown fox.",
model="text-embedding-3-large",
vector=embedding,
)
verifier = Verifier({signer.key_id: signer.public_key_bytes()})
result = verifier.verify(pin, source="The quick brown fox.", vector=embedding)
assert result.ok
The adapter protocol is intentionally thin. vectorpin audit-* commands print a JSON summary on stdout and exit non-zero on any verification failure, so they compose cleanly into CI or a cron job.