OGuardAI
Security

Security Guarantees

What OGuardAI guarantees, what it does not guarantee, and what remains the customer's responsibility

This document states explicitly what OGuardAI guarantees, what it does not guarantee, and what remains the customer's responsibility.

Canonical Terminology

These terms have precise meanings throughout OGuardAI. If you encounter them in APIs, configs, logs, or documentation, they mean exactly this:

TermDefinition
TokenizedA raw PII value has been replaced by a semantic token {{type:id:cap}} in the output text. The original value is stored in the encrypted session blob. Tokenization is deterministic: the same value always produces the same token within a session.
RestorableA tokenized value CAN be restored to its original form using the session state blob. Restorability depends on: (1) the session blob being valid and non-expired, (2) the entity not being revoked, (3) the policy allowing restore for that entity type and output channel. A value that was tokenized is restorable until its session expires or the entity is revoked.
RevokedA value has been permanently marked as non-restorable. During rehydrate, revoked entities return [DELETED] instead of the original value. Revocation is persistent (survives server restart) and uses HMAC-SHA-256 hashing -- no raw PII is stored in the revocation table. Revoking a person cascades to all linked entities (email, phone, address) via belongs_to relationships.
DeletedIn OGuardAI's context, deletion means the original value is no longer recoverable by any means. This is achieved by: (1) revoking the entity, (2) allowing the session blob to expire, or (3) dropping the session secret. OGuardAI does NOT delete data in external systems (vector stores, databases, log sinks) -- that is the integrator's responsibility.
BlockedThe policy engine has determined that this entity type is not allowed in the given context. Blocked entities are removed from the output entirely (not tokenized, not passed through). The entities_blocked counter tracks this. In the output guard context, "blocked" means the entire response is rejected because new high-sensitivity PII was detected.
MaskedA value is shown in an obscured form. As a restore mode (masked), the first and last characters are kept and the middle is replaced with * (for example 4*********1); the raw value stays in the sealed session, so a channel whose policy resolves to a more permissive mode can still restore it. In the output guard, masking replaces newly generated PII with a type label like [EMAIL] or [SSN]. The masked text itself is one-way: the original cannot be recovered from it, only from the session.
RedactedNOT the same as masked. Redaction removes a value non-restorably: the redact policy action replaces it with [REDACTED:type] at transform time and purges it from the session, so no channel and no restore mode can ever bring it back. The none restore mode similarly returns [REDACTED] in place of the value at rehydrate. Masked, by contrast, is a per-channel view: the sealed session still holds the raw value.
DetectedAn entity span has been identified by the detection engine (regex, NER, or both). Detection does not imply tokenization -- the policy engine decides what happens to each detected entity (tokenize, block, or pass through).
Passed throughThe value is listed in the policy whitelist, so the raw value remains in the output unchanged. This is a deliberate policy decision, not a detection failure.
Session stateAn AES-256-GCM encrypted blob containing the token map (token-to-original-value mappings), session metadata, and policy reference. The blob is opaque to clients and LLMs. It is the ONLY artifact that can restore tokenized values.
Trace IDA UUID v4 identifier that correlates all operations in a request lifecycle: transform, proxy pass, tool calls, rehydrate, output guard, and revocation. Client-supplied or auto-generated. Use the same trace_id across transform and rehydrate to enable full incident reconstruction.

What OGuardAI GUARANTEES

Data Protection

  • Detected, non-whitelisted PII values are never left raw in safe_text; they are tokenized or removed (verified by 2,600+ Rust unit and integration tests, plus the Python, TypeScript, and Java SDK suites). This is scoped to what detection finds: a value the detector misses (see Detection Completeness below) and a value a policy deliberately whitelists (Passed through) can still appear, so treat this as a data-minimization control over detected entities, not an absolute guarantee
  • Tokenized text uses ONLY canonical semantic tokens of the form {{type:id:cap}}, carrying a per-token capability cap on the wire ({{type:id:cap}}, see Token Protocol); it never contains raw values or ad-hoc placeholders
  • Sealed session blobs are encrypted with AES-256-GCM (authenticated encryption)
  • Tampered session blobs are always rejected (cryptographic verification)
  • Expired sessions produce clean errors, never data leaks

Detection

  • Builtin mode (regex): the named types plus the built-in custom detectors, all detected deterministically, same input = same output
  • NER mode (GLiNER): adds person/company/location plus policy-defined zero-shot labels, detection quality depends on model
  • Both modes: detection is applied to ALL string content in the configured scan scope

Restore

  • full restore is byte-for-byte identical to the original value
  • Each of the 6 restore modes produces predictable, documented output
  • Channel-specific restore rules are applied deterministically per policy
  • Caller-gated restore overrides (caller_role / caller_purpose) are re-authorized at rehydrate against the authenticated rehydrate caller, never the transform caller or a request-supplied field. A caller lacking the role degrades to the policy floor and a restrictive override still tightens, so a session_state is not a bearer token for elevated restoration
  • Pre-restore raw-leak guard: before returning restored output, OGuardAI re-scans the resolved text with the {{type:id:cap}} token spans stripped out. If a session's original value for a high-value structured type (email, phone, ssn, iban, credit_card, passport, health_id, date_of_birth) reappears as raw text, meaning the model echoed the value instead of its token and bypassed controlled restoration, rehydrate hard-blocks with GUARDAI_POLICY_DENIED. These types are detected deterministically, so a raw re-appearance is a genuine leak, not a common-word coincidence. NER-typed values (person, company, location) that reappear are logged as a warning and not blocked, because a common word the model over-tagged can legitimately recur in prose; the post-rehydrate output guard remains the backstop for those. A revoked value is the exception: its raw reappearance is blocked regardless of type, because a revoked value is deleted and must never reappear, so the warn-only path above applies only to non-revoked full-restore NER values

Internationalized Restoration

Restored values are written back into surrounding text the LLM produced. Mixed-script and partial restoration can introduce two classes of problem: a restored value reordering or spoofing the text around it, and a partial reveal that exposes the wrong part of a name. OGuardAI applies the following fail-closed safety behaviors. They reduce risk, they do not promise that every locale is rendered perfectly.

Bidi isolation of restored values (Trojan-Source defence). When a restored value contains right-to-left script or explicit bidirectional control characters, the rehydrate engine wraps it in a Unicode isolate, U+2068 (First Strong Isolate) before the value and U+2069 (Pop Directional Isolate) after it, and strips any inner bidi-control characters so the value cannot terminate or escape its own isolate. The result: a restored value cannot reorder, or be reordered by, the text around it. Left-to-right only values, including Latin, CJK, and email addresses, are returned unchanged. This is a defence against bidi / Trojan-Source style spoofing, not a claim that all directional rendering is correct in every renderer.

Family-first name masking in partial restore. Partial restore of a person name normally reveals an initial plus surname, for example J. Schneider. That ordering assumes a Western given-then-family layout. For CJK script and for the family-first languages zh, ja, ko, vi, and hu, that abbreviation would reveal a misidentified name part, so partial restore masks the name instead of producing a wrong F. Surname. Given-first scripts, including Arabic and Hebrew, keep the initial-plus-surname reveal. This applies only when the channel-resolved mode is Partial, which is gated by the channel ceiling.

30-language abstract-label floor with safe fallback. The built-in restore floor ships abstract labels (person, company, address, location, and related types) for all 30 supported languages, as data in an embedded floor file rather than hardcoded control flow. When the value's language is unknown or has no entry, lookup falls back to the English label, and if even that is missing it returns a fixed redacted label. No real value is interpolated into an abstract label. Any language can be added or an existing one tightened through the restore_templates config overlay (or GUARDAI_RESTORE_TEMPLATES). The overlay may only add or override entries on top of the floor: a malformed overlay, or one whose abstract label carries a {name} placeholder, aborts startup, fail closed.

Revocation

  • Revoked entity values ALWAYS return [DELETED] during rehydrate
  • Cascade revocation: revoking a person suppresses ALL linked entities (email, phone, address)
  • Revocation is persistent (file backend, or a shared Redis backend via revocation_backend: redis)
  • Revocation uses HMAC-SHA-256: no raw PII stored in the revocation table

Revocation Contract

The following are the canonical, binding guarantees for OGuardAI's revocation system:

  1. Revocation affects FUTURE rehydrate calls only: outputs already delivered to end users or downstream systems cannot be clawed back.
  2. Sealed session blobs remain decryptable after revocation, but any revoked value resolves to [DELETED] instead of the original.
  3. Revoking a person entity cascades to every entity linked via belongs_to relationships (email, phone, address): all linked entities also resolve to [DELETED].
  4. Multiple entities can be revoked in a single API call (bulk revoke).
  5. Revocation state survives server restart when using the file backend. Cross-instance sharing uses the Redis backend (revocation_backend: redis).
  6. Vector stores, external databases, and application caches must still delete their own copies of data. OGuardAI cannot reach into external systems.
  7. Revocation is irreversible: there is no "un-revoke" operation.
  8. The revocation table stores only HMAC-SHA-256 hashes of entity values: no raw PII is ever stored in the revocation table itself.

Session Security

  • Cross-tenant session access is always rejected
  • Removing a session key from the keyring instantly invalidates every blob sealed under it; a graceful rotation that keeps the old key in the ring lets in-flight sessions stay valid until their TTL
  • Session TTL is enforced: expired blobs cannot be unsealed

Policy

  • Policy rules are evaluated deterministically for every entity
  • Policy inheritance resolves child > parent > default
  • Policy integrity can be verified via HMAC signatures

What OGuardAI DOES NOT Guarantee

Detection Completeness

  • No detection system catches 100% of PII in all contexts
  • Person/company/location detection REQUIRES the Python NER sidecar
  • In builtin-only mode, person names, company names, and locations are NOT detected
  • OCR text extraction is best-effort: noisy scans may produce detection gaps
  • Custom or domain-specific entity types beyond the built-in types (regex named formats and built-in custom detectors, plus Person, Company, and Location via the NER sidecar) are not detected

External System Deletion

  • OGuardAI does NOT control vector stores, external databases, or log sinks
  • RAG chunk deletion in vector stores is the application's responsibility
  • Log retention and purging is managed by the logging infrastructure
  • OGuardAI provides guidance and signals, but cannot enforce external cleanup

Provider Behavior

  • LLM output quality depends on the provider (OpenAI, Anthropic, etc.)
  • Token damage patterns vary by provider and model version
  • Token repair is best-effort with 3-stage pipeline (strict -> repair -> fuzzy)
  • Hallucinated tokens are flagged as unresolved, never fabricated

Performance Under Load

  • Latency depends on detector mode, payload size, and NER sidecar availability
  • NER mode adds roughly 80ms p50 and up to several hundred ms p99 per request (indicative, depending on hardware, model, and text length)
  • If NER sidecar is configured but unavailable, each request adds up to the configured NER timeout (detector.timeout_secs)
  • Rate limiting is per-instance, not shared across instances

Distributed Consistency

  • Sealed sessions are stateless: work across any number of instances
  • Revocation with the file backend is per-instance; the Redis backend (revocation_backend: redis) shares it across instances
  • Rate limiting is per-instance (use API gateway for global limits)
  • Metrics are per-instance (use Prometheus for aggregation)

Customer Responsibilities

ResponsibilityWhy
Start NER sidecar if person/company/location neededNER is optional
Delete vector store chunks after RAG deleteOGuardAI doesn't own vector stores
Rotate session keys on compromiseKey management is ops responsibility
Configure appropriate policy for use casePolicy selection affects protection level
Monitor health endpointDetect degraded mode early
Set auth.mode to non-dev for productionDev mode bypasses auth
Set session.secret to a real secretDefault secret is warned about
Configure log retentionOGuardAI emits audit events, retention is infra

Failure Modes

FailureOGuardAI Behavior
NER sidecar downFalls back to builtin regex (person/company/location missed)
Invalid session blobClean error returned, no data leaked
Malformed LLM output3-stage token repair attempted, unresolved tokens flagged
Policy not foundDefault policy applied
Output guard catches new PIIMasked or blocked per config
LLM echoes a raw high-value session value at rehydrateRestoration hard-blocked with GUARDAI_POLICY_DENIED (pre-restore raw-leak guard)
LLM echoes a raw NER-typed session value at rehydrateLogged as a warning, not blocked (may be a common word); output guard remains the backstop
Rate limit exceededHTTP 429 with Retry-After header
File too largeHTTP 400 rejection
Revoked entity in rehydrateReturns [DELETED]

What Depends on NER Mode

Not all features work identically in builtin-only vs NER mode. This matrix clarifies:

CapabilityBuiltin (regex)NER (GLiNER)Notes
Email detectionYesYesRegex in both modes
Phone detectionYesYesRegex in both modes
SSN / IBAN / CCYesYesRegex in both modes
IP / URL / DOBYesYesRegex in both modes
Order / TicketYesYesRegex in both modes
Person nameNoYesRequires NER sidecar
Company nameNoYesRequires NER sidecar
LocationNoYesRequires NER sidecar
Address (structured)PartialYesRegex detects 7 country formats; NER detects any language
Entity linkingLimitedFullPerson-to-entity links need person detection (NER)
Cascade revocationLimitedFullCascade from person to linked entities needs NER for person detection
Detection latency~1-2ms~80ms p50, up to several hundred ms p99Indicative, hardware/model/length-dependent; NER adds model inference time
Determinism100%Model-dependentSame input may produce slightly different NER confidence scores across model versions

Bottom line: Builtin mode is fast and deterministic but misses person/company/location. NER mode catches more entity types but adds latency and model dependency.

What Depends on Policy

FeatureDefault PolicyStrict PII PolicyEnterprise Policy
Tokenize emailYesYesYes
Tokenize phoneYesYesYes
Block SSNNoYesPer-channel
Block IBANNoYesPer-channel
Pass through URLYesNoPer-channel
Restore modeFullMaskedPer-channel
Output guard actionMaskBlockPer-entity-type
Cascade revocationAvailableAvailableAvailable
Shadow modeConfig-levelConfig-levelConfig-level

Bottom line: Policy controls what happens to detected entities. Detection is independent of policy: it runs first and finds everything. Policy then decides: tokenize, block, or pass through.

Lifecycle of a Protected Value

At each stage, the value can only move forward (detected -> tokenized -> restored). It cannot be "un-tokenized" without the session blob, and it cannot be "un-revoked" once revoked.