Entity Storage
How Secured stores and reuses global entities, thread entities, and runtime-generated aliases.
Overview
Secured has a storage layer for previously known sensitive entities and their approved replacements. In the codebase this is implemented by the vault subsystem, but conceptually it is the library's entity storage system.
That system exists so the library can answer questions like:
- "Have we already assigned a global alias for this entity?"
- "Does this thread already know the replacement for this value?"
- "If we generated a new alias a moment ago, should we reuse it on the next obfuscation call?"
Without this layer, obfuscation would only use replacement pools and would generate fresh stand-in values more often.
The four storage layers
Secured can combine four different layers of stored entities:
- Global entities Shared encrypted entity mappings fetched from your backend for the whole workspace or product surface.
- Thread entities Encrypted mappings scoped to a specific thread, conversation, or case.
- Runtime thread entities Mappings generated by the SDK during the current thread lifecycle and cached locally.
- Pending entities Mappings generated before a thread is known, waiting to be attached to the first thread context.
All four layers use the same logical shape:
interface VaultEntry {
original: string
replacement: string
type: string
}Why "global entities" matter
Global entities are the part most likely to feel "new" at the product level. They let your backend publish a canonical replacement for important values so the frontend SDK can reuse them during obfuscation.
Example:
{
original: 'ProjectFalcon',
replacement: 'PROJECT_TOKEN',
type: 'CUSTOM',
}Once this exists in the global entity store, any later obfuscation of ProjectFalcon can reuse PROJECT_TOKEN instead of inventing a new alias.
Precedence rules
When the library tries to resolve a replacement, it checks the most specific stored layer first.
If a thread context is active:
- runtime thread entities
- persisted thread entities
- global entities
- generated replacement fallback
If no thread context is active:
- pending entities
- global entities
- generated replacement fallback
This is why a thread-specific alias can override a workspace-level alias.
How entities are matched
Stored entities are matched by normalized original text plus normalized type.
That means the storage system canonicalizes entity types so equivalent labels can still match:
PER,NAME,PERSON_NAME->PERSONLOC,LOCATION->GPEORGANIZATION->ORG
So a stored global entity with type LOC can still satisfy a detected entity of type GPE.
What gets persisted by your backend vs by the SDK
Your backend is responsible for persisted global and optionally persisted thread entities.
The SDK is responsible for:
- decrypting persisted snapshots
- reading and caching them client-side
- generating new runtime entities when no stored match exists
- reusing those runtime entities on later obfuscation calls
- migrating pending entities into a thread when one is later selected
Backend snapshot model
The default repository expects encrypted snapshots rather than plaintext entries.
Global entities are fetched from:
GET /ai/sensitive-keysThread entities are fetched from:
GET /ai/threads/:threadId/sensitive-keysThe client decrypts the encrypted payload with the configured masterKey, then exposes the decrypted entries through the vault manager.
Lazy loading behavior
Global entities are loaded during vault initialization.
Thread entities are loaded lazily:
- either when you call
setThreadContext(threadId) - or when you pass
threadIddirectly toobfuscate(..., { threadId }) - or when you explicitly call
client.vault.loadThreadSnapshot(threadId)
This keeps thread-specific fetches off the critical path until they are actually needed.
Runtime-generated entities
If no stored entity exists, the client generates a replacement from the configured replacement pools and immediately remembers it in local runtime storage.
That means the next obfuscation call can reuse the same replacement instead of generating a new one.
This behavior is what gives Secured stable aliases even before the backend has persisted anything.
Pending entities
If obfuscation happens before a thread is known, generated entities are temporarily stored as pending.
Once you later call setThreadContext('thread-123'), those pending entities are migrated into that thread's runtime entity layer and stop living in the pending bucket.
Cache namespaces
Entity storage can be namespaced with cacheNamespace, which isolates cached snapshots for different workspaces or tenants.
This prevents one workspace's global entities from bleeding into another workspace's client cache.
Where to configure it
The entity storage system is configured through PrivacyClientConfig.vault.
See:
- Vaults for setup
- PrivacyClient for the API surface
- React Hooks for
useVault()