A note on language used throughout this document: where we discuss regulatory regimes (SOC 2, ISO 27001, HIPAA, GDPR, DPDP and similar), we describe the architecture as designed to support or intended to help you meet those regimes. Aegis holds no third-party certification or attestation, and this document makes no such claim. See Section 7 and Section 9.
1.Executive summary
Aegis sits between your users and one or more large language models (LLMs). Its core function is to ensure that personally identifiable information never reaches an LLM in cleartext.
The mechanism is a deterministic redaction pipeline. Before any prompt is
forwarded to a model — whether a frontier provider, an open model, or
the sovereign local model — Aegis detects sensitive spans, replaces each
with an opaque placeholder token (for example <<EMAIL_1>>),
stores the real value in a session-scoped, encrypted vault, and forwards
only the placeholder-bearing text to the model. When the model
answers, Aegis re-injects the real values into the response — but only
for the verified owner of that session. The model never sees, stores, or learns
the underlying personal data.
Around that core, Aegis layers governance (role-based access control, topic firewalls, supervisor approvals, multi-tier quotas), defence-in-depth guards (prompt-injection detection, hallucinated-identifier detection), encryption at rest, and per-request auditing that records what types and how many PII items were handled — never the values themselves.
Key design facts a reviewer should take away:
- Detection runs before forwarding, and fails closed. If detection cannot complete, the request is refused rather than forwarded. (
src/Clab.Pipeline/Stages/PiiDetectionStage.cs.) - The audit log stores counts and types only. The
tbAuditLog.PiiCountsJsoncolumn holds a per-type count map; no PII value is ever written there. (db/100_schema.sql,src/Clab.Data/Repositories/SpAuditLogger.cs.) - Message content is encrypted at rest with AES-256-GCM under a per-tenant data-encryption key, in production today. (
src/Clab.Security,db/320_tenant_encryption.sql.) - Tenant data is never used to train the shared model unless the tenant explicitly opts in (default off). (
db/310_tenant_sovereignty.sql.)
2.Threat model & design principles
2.1 What we defend against
- Leakage of PII to third-party model providers. The primary threat. A user pastes a customer record, a medical note, a contract; without controls, that data leaves your boundary in cleartext to a model you do not operate.
- Cross-tenant and cross-user data exposure. One tenant's (or one user's) sensitive data being returned to another party.
- Database compromise / raw dump. An attacker who obtains a database backup or file-level copy.
- Prompt injection. A user (or injected content) attempting to override the assistant's instructions or governance.
- Model fabrication of sensitive identifiers in responses.
- Policy circumvention — sending disallowed content or exceeding quotas.
2.2 Principles
Fail closed. Every gate, when uncertain, denies rather than
permits. If the PII detector throws, the orchestrator does not forward the
prompt — it returns a DetectorFailure outcome
(PiiDetectionStage.cs). If a PII rule times out, the engine logs
and re-throws rather than continuing
(src/Clab.PiiGuard/Engine/PiiDetectionEngine.cs). If at-rest
encryption is enabled but the master key is missing or the wrong length,
dependency-injection registration throws at startup rather than silently
running unencrypted (src/Clab.Security/SecurityOptions.cs,
SpTenantKeyStore decodes the key eagerly in its constructor). A row
flagged encrypted whose payload is not valid ciphertext is surfaced as a
cryptographic failure, never returned as text
(src/Clab.Security/MessageCipher.cs).
Least privilege. Re-injection of real PII values is gated on
session ownership: only the (tenantId, userId) that owns a session
can have that session's placeholders restored
(src/Clab.PiiGuard/Vault/PiiReinjectionService.cs,
SessionOwnershipStage). BYOK secrets and decryption keys live in
process memory and gitignored configuration, never in the database in usable
form.
Defence in depth. PII protection does not rely on a single technique. The redaction pipeline composes deterministic regex/rule packs (one universal pack, ten country/region packs — nine single-country plus an EU-GDPR regional pack — three domain packs, and tenant-custom rules) with an optional transformer NER model, then adds independent guards on the way in (prompt injection) and on the way out (PII-leak and hallucinated-identifier validation of the model's response). A failure or gap in any one layer is backstopped by others.
Stored-procedure-only data access. The entire data-access
layer is stored-procedure-based with tenant filtering in each procedure; there
is no ORM and no inline SQL. This narrows the attack surface and makes
data-locality and isolation work a port rather than a rewrite
(docs/strategy/SOVEREIGN-RESIDENCY-DESIGN.md).
3.Data protection: the redaction pipeline
3.1 The guarantee
The single most important property of Aegis: the LLM sees placeholders, not people. Real personal data is removed before forwarding and restored only for the verified owner afterwards.
3.2 Pipeline flow
The orchestrator runs an ordered sequence of stages and writes an append-only
audit event per request (best-effort; a failed write is logged and never blocks
the request — src/Clab.Pipeline/PipelineOrchestrator.cs,
src/Clab.Pipeline/ServiceCollectionExtensions.cs,
src/Clab.Data/Repositories/SpAuditLogger.cs). The PII-relevant
stages, in order:
- Session ownership is claimed/verified.
- Access policy firewall (
PolicyGuardStage) enforces the tenant's work-hours window, IP-CIDR allow/block lists, and country allow/block lists; when an allow-list is active and the caller's location or IP cannot be determined, it fails closed. - Prompt-injection guard inspects the input.
-
PII detection + sanitisation (
PiiDetectionStage.cs):- The detector runs all applicable rule sets. On any detector exception the request is refused (fail closed) — it is never forwarded to a model.
- The sanitiser (
src/Clab.PiiGuard/Engine/PiiSanitizer.cs) walks the detected spans, replaces each with a vault placeholder token, stores the real value in the vault, and produces the sanitised text. From this point on, downstream stages operate only on the sanitised text.
- Strict-mode guard (
PiiModeGuardStage.cs): if the tenant's policy isblock(orredactwith specific blocked types), a detected hit refuses the prompt entirely — not even placeholders are forwarded. - Intent resolution, quota gates, topic firewall, semantic cache.
- LLM call (
LlmCallStage.cs): the sanitised, placeholder-bearing prompt is sent to the routed provider. - Response validation (
ResponseValidationStage.cs): the model's answer is checked for leaked PII and hallucinated identifiers before anything is returned. - Re-injection (
PiiReinjectionStage.cs): placeholders in the answer are restored to real values — only if the caller owns the session.
3.3 Detection layers
Detection composes several rule sets per tenant (PiiDetectionEngine.ResolveRuleSets):
- Universal rules — always on (e.g. email, payment-card patterns).
- Country/region packs — ten built-in packs registered in
src/Clab.PiiGuard/Rules/RuleSetRegistry.cs: nine single-country packs (UAE, India, Singapore, Saudi Arabia, Qatar, Kuwait, Bahrain, Oman, and the United States) plus one EU-GDPR regional pack covering the European Union. - Domain packs — three: Medical, Legal, Finance.
- Tenant-custom rules — per-tenant regex rules added at runtime.
In total the registry ships one universal pack, ten country/region packs (nine single-country plus the EU-GDPR regional pack), and three domain packs; tenant custom rules are layered on at runtime.
Overlapping detections are resolved deterministically (higher severity, then longer, then earlier span wins), so the same input always sanitises the same way.
Transformer NER augmentation (optional, off by default). A
multilingual DistilBERT model
(Davlan/distilbert-base-multilingual-cased-ner-hrl) served via ONNX
Runtime on CPU can be enabled to catch named entities (PER/ORG/LOC) that pattern
rules miss (src/Clab.PiiGuard/Ner/NerEntityRecognizer.cs,
NerAugmentedDetector.cs). It is augmentation, not the
authoritative detector: NerOptions.Enabled defaults to
false, and the deterministic regex/rule engine always runs first and
remains authoritative. NER spans are merged in only where they do not overlap a
more specific rule hit. Critically, NER can never weaken
detection: if the model is disabled, missing, or fails inference, the
augmenter logs and falls back to the regex spans — it never throws out of
detection. The model is trained on ten languages
(ar/de/en/es/fr/it/lv/nl/pt/zh); Hindi is not one of them, so
Hindi PII is covered by the deterministic Hindi rule packs rather than the model.
Confidence threshold and windowing are configurable
(NerOptions.cs). See Section 9 for an honest note on language
coverage.
3.4 The vault
Detected values are stored in a session-scoped vault (src/Clab.Data/Repositories/SpPiiVault.cs, stored-procedure backed):
- Each entry stores the value encrypted (
EncryptedValue, a byte array) using AES-256-GCM (src/Clab.PiiGuard/Vault/AesGcmEncryption.cs), keyed and hashed per session. - Each entry records its owning
TenantIdandOwnerUserId. - Ownership is enforced. Re-injection calls
IsOwnerAsync; if the caller is not the session owner, the placeholders are left unrestored and the request is blocked withBlockedOwnership(PiiReinjectionService.cs,PiiReinjectionStage.cs). - Vault entries carry an expiry, supporting retention limits.
3.5 Audit stores counts, not values
The audit log is append-only and records, per request, the types and
counts of PII detected — never the underlying values. The
orchestrator builds a Type -> count map
(PipelineOrchestrator.Finalise); the logger serialises that map into
the PiiCountsJson column and writes nothing else PII-derived
(SpAuditLogger.cs). The table itself is documented in
db/100_schema.sql as "types and counts only — NEVER values." A
reviewer auditing a leaked database backup would find detection statistics, not
personal data, in the audit trail.
4.Encryption at rest
4.1 Envelope scheme
Aegis encrypts tenant message content at rest using AES-256-GCM authenticated encryption in an envelope scheme (src/Clab.Security/AesGcmEnvelope.cs):
- A fresh 96-bit (12-byte) nonce is generated per encryption from a cryptographic RNG.
- The wire layout is
[12-byte nonce][16-byte GCM tag][ciphertext]. - Any tamper of nonce, tag, or ciphertext — or use of the wrong key — fails GCM tag verification and raises a
CryptographicException. The helpers never return unauthenticated plaintext.
4.2 Per-tenant DEK, master KEK
The scheme is two-tiered:
- Each tenant has its own 32-byte data-encryption key (DEK), generated on first use, wrapped (encrypted) under the master key-encryption key (KEK), and persisted in
tbTenantKeyas the wrapped bytes plus a discrete nonce (db/320_tenant_encryption.sql,src/Clab.Data/Repositories/SpTenantKeyStore.cs). The unwrapped DEK lives only in process memory. - The master KEK is held only in gitignored configuration, never committed to the repository and not stored in the database (
Clab:Security:MasterKeyBase64inappsettings.Local.json;src/Clab.Security/SecurityOptions.cs). The cryptography never happens in T-SQL — the database only ever stores already-wrapped keys and already-encrypted content. A raw database dump is therefore opaque per tenant; only the application, holding the KEK, can unwrap a DEK and read content.
4.3 What is encrypted, and the production status
tbMessage.SanitizedContent — the stored conversation content,
which already holds placeholder text rather than raw PII — is encrypted at
rest. This is enabled in production now (db/320,
src/Clab.Security/MessageCipher.cs). Encryption is gated by a master
switch (Clab:Security:EncryptMessages); when on, the KEK must decode
to exactly 32 bytes or startup fails (fail closed). The same envelope protects
BYOK secrets (Section 5.3).
4.4 Honest limit: key rotation and backfill
- DEK rotation is not yet implemented. There is a single active key per tenant. The schema anticipates rotation (a
KeyVersioncolumn and an insert-or-replace upsert that supports re-wrap), but an automated rotation process has not been built. - Pre-encryption rows may remain plaintext until backfilled. Encryption was introduced additively: each message row carries an
IsEncryptedflag, existing rows default to plaintext, and an in-application backfill job (usp_Message_BackfillPage/usp_Message_SetEncrypted, version-controlled indb/320) converts them. Until that optional backfill runs for a given tenant, some historical rows may still be plaintext.
These limits are restated in Section 9.
5.Data sovereignty, training, and BYOK
5.1 We never train on your data unless you opt in
A tenant's prompts and outputs are never used to train the shared CLAB model unless that tenant explicitly opts in. The opt-in defaults to off.
This is enforced at the data layer, not merely promised in policy. The
tbTenant.TrainingOptIn flag defaults to 0, and
migration 310 flips the historical default and sets every existing
tenant to 0 so nobody contributes without a deliberate, recorded
opt-in (db/310_tenant_sovereignty.sql). The shared-corpus export
procedure (usp_Corpus_ExportContributed) joins on
TrainingOptIn = 1 — opted-out tenants' data is structurally
excluded from the export that feeds shared training. Exports return sanitised
(placeholder) content only, never raw PII; the Python export utility is
python/clab_trainer/export_tenant_corpus.py.
5.2 Private per-tenant adapters
A tenant may train a private adapter on only their own data. The
per-tenant corpus export (usp_Corpus_ExportForTenant) returns only
that tenant's sanitised messages, and tbTenant.PrivateModelTag
records the name of that tenant's own private model. This path does not touch,
and is not mixed into, the shared model.
5.3 Bring your own key (BYOK)
A tenant may supply their own upstream provider API key (for
example their own Anthropic or OpenAI key). The gateway then uses that key for
that tenant's calls to that provider instead of the shared platform key
(src/Clab.Gateway/Byok, db/330_byok.sql).
- The tenant's key is encrypted at rest under the same per-tenant DEK / master KEK envelope as message content;
tbTenantProviderKeystores only the wrapped bytes plus a nonce (SpTenantProviderKeyStore.cs). A raw database dump never exposes a usable key. - Listing configured keys never returns key bytes — only the provider id, a "has key" flag, and a non-secret last-4 display hint (
usp_TenantProviderKey_List). - At call time the key is resolved per
(tenant, provider); a null result falls back to the platform key. The secret is passed only to the provider call and is never written to the audit trail (src/Clab.Pipeline/Stages/LlmCallStage.cs).
5.4 Sovereign local inference
Aegis ships its own model, CLAB-Aegis (a QLoRA fine-tune of an open base), served via a local Ollama runtime, alongside other open models. When a tenant uses the local model, inference is fully local — nothing leaves the host. Frontier providers remain available via BYOK or shared platform keys for tenants who want them.
6.Access control & governance
- Role-based access control (RBAC) with API keys stored hashed, roles and per-endpoint permissions (
db/140_rbac.sql; resolver and middleware insrc/Clab.Auth/ API layer). - Topic firewall (
src/Clab.Pipeline/Stages/TopicGuardStage.cs): the sanitised prompt is classified into a coarse topic and checked against the tenant's allow-list / block-list policy. Off-policy prompts are blocked withBlockedTopic. The firewall runs on the sanitised text so placeholders cannot trip the classifier, and runs before the cache so an off-topic prompt cannot be served from cache. - Supervisor approval workflows: prompts on configured topics are queued for review (
PendingApprovaloutcome) and surfaced to the user once reviewed. - Three-tier quotas: tenant, per-user, and department caps are enforced as pre-LLM gates so the system fast-fails before incurring a model call (
QuotaGuardPreStage,UserQuotaGuardPreStage,DeptQuotaGuardPreStage), with atomic post-call increment. Blocked messages tell the user which ceiling was hit. - Strict redaction modes (
PiiModeGuardStage.cs): per tenant,redact(default — placeholders forwarded, values vaulted) orblock(any detected PII refuses the prompt), with the option inredactmode to hard-block specific PII types. Industry policy presets configure these defaults. - Prompt-injection guard (
PromptInjectionStage.cs) on input and hallucinated-identifier / PII-leak guard (ResponseValidationStage.cs) on output, both independent of the redaction layer.
7.Auditability & compliance support
Aegis produces evidence designed to support a compliance programme:
- Monthly compliance report (PDF): detections by type (counts only), outcomes, approvals, and alerts.
- Audit export in CSV and JSON for ingestion into your own SIEM or governance tooling.
- DSAR support: per-subject data export and deletion endpoints.
- Public status page at
/status.html.
The append-only audit trail (Section 3.5) is the foundation: an audit event is written per request (best-effort — a failed write is logged and never blocks the request) recording outcome, provider/model, token and cost figures, latency, and PII type/count metadata — never values.
On regulatory regimes. Aegis is designed to support, and helps you meet the obligations under, regimes such as SOC 2, ISO 27001, HIPAA, GDPR, and India's DPDP — through PII minimisation before third-party processing, encryption at rest, access control, data-subject tooling, and auditability. Aegis does not hold any certification or attestation under these regimes, and nothing in this document should be read as a claim of certification, compliance, or audit attestation. See Section 9.
8.Deployment & residency
Today. Aegis runs as a single-region SaaS on the founder's
infrastructure (.NET 10 on IIS / Windows, SQL Server 2025). Each tenant record
carries a DataRegion column (db/230_ops.sql), set at
creation and validated against a known set of region codes (AE, IN, EU, SG, US,
KSA, QA, KW, BH, OM); this tags the tenant's declared residency and is surfaced
for transparency.
Roadmap (designed, not yet built). A three-tier residency model
— region-pinned SaaS, dedicated database per tenant, and a fully sovereign
on-premise appliance where data stays entirely on the client's servers — is
designed in
docs/strategy/SOVEREIGN-RESIDENCY-DESIGN.md. That document is
explicitly marked a design, not a build commitment. The
stored-procedure-only DAL makes these tiers a port rather than a rewrite, but they
are not shipping features today. There is currently a single
region with no high-availability or multi-region deployment.
9.Current limitations & roadmap
We state these plainly because serious buyers test for them, and because a vendor that hides limits cannot be trusted on the claims it does make.
- No third-party security audit or penetration test has been performed. Aegis has not undergone an external security audit or pen test, and we make no such attestation. Internal red-teaming has been conducted; that is not a substitute for third-party assurance.
- No certifications held. Aegis is not SOC 2, ISO 27001, HIPAA, GDPR, or DPDP certified. We describe the architecture only as designed to support / helps you meet these regimes (Section 7).
- DEK rotation is not implemented. One active data-encryption key per tenant; the schema anticipates rotation but no automated rotation process exists yet (Section 4.4).
- Pre-encryption rows may remain plaintext until the optional in-application backfill runs for a tenant (Section 4.4).
- Single region, no HA / multi-region. Dedicated-database-per-tenant isolation and the sovereign on-prem appliance are designed but not built (Section 8). We do not claim a 99.9% uptime SLA, multi-region, or high availability.
- NER language coverage is bounded by the trained model. The optional transformer NER model (
Davlan/distilbert-base-multilingual-cased-ner-hrl, off by default) detects PER/ORG/LOC across ten trained languages (ar/de/en/es/fr/it/lv/nl/pt/zh). Hindi is not a trained language for this model; Hindi PII is covered by deterministic Hindi rule packs and tenant-custom rules rather than the model. NER is an augmentation, never the sole detector — the deterministic regex/rule engine is always authoritative.
Where these items are scheduled, they appear in the strategy roadmap
(docs/strategy/NEXT-12-MONTHS.md,
SOVEREIGN-RESIDENCY-DESIGN.md).
Document version: v1.0 — 2026-06-19
Prepared by: CLAB / Aegis engineering, for procurement and security review.
Verification basis: Every claim in this document was checked against the Aegis source tree (src/Clab.PiiGuard, src/Clab.Pipeline, src/Clab.Security, src/Clab.Gateway/Byok, db/100, db/310, db/320, db/330) at the date above. Code evolves; re-verify against the named files before relying on a specific claim in a contractual context.