RAG data protection
- Tenant isolation: forced RLS on
kb_documents,kb_chunks,kb_quarantine_reviews; one Qdrant collection per tenant. - Document and chunk ACL checked in the vector store and again before the context is built.
- Source right: active connector required at ingestion and query (403
source_permission_denied). - Poisoning: quarantine, review by
security_officer(data steward), audited. - Encryption: text of
restrictedandsecretchunks as a Tink envelope, rotation and re-wrap. - Sensitive cache: Tink encrypted, key bound to the tenant, Redis never the source of truth.
- Banking test data: all fictitious.
Verified Docling and PaddleOCR toolchains (DATA-010, DATA-011)
Setup (separate virtual environments, pinned versions, official sources only):
bash tools/rag/setup-docling.sh # /tmp/toolchains/docling-venv + /tmp/toolchains/docling-models
bash tools/rag/setup-paddleocr.sh # /tmp/toolchains/paddleocr-venv + /tmp/toolchains/paddleocr-models
- Docling 2.145.0 (MIT), docling-core 2.105.0 (MIT), docling-ibm-models 4.0.4 (MIT), torch 2.9.1+cpu (BSD-3-Clause, official index
https://download.pytorch.org/whl/cpu), python-docx 1.2.0 (MIT). - paddlepaddle 3.3.1 CPU (Apache-2.0), paddleocr 3.7.0 (Apache-2.0), paddlex 3.7.2 (Apache-2.0), from PyPI.
- The scripts write
models.sha256(sha256 of every weight file) and print the licence of every model card.
Model weights and licences:
| Model | Official source | File | sha256 | Licence |
|---|---|---|---|---|
| docling-layout-heron | Hugging Face docling-project/docling-layout-heron | model.safetensors | 00333a43451945aaf89db8ca9c0a17e75d1537c17db60fdb91aa95f4c7929e0c | Apache-2.0 |
| docling-layout-heron-onnx | Hugging Face docling-project/docling-layout-heron-onnx | model.onnx | 59c81a3a2923042d85034ffc487f8f47e4854117e879aef89b2b9f728fb4922a | Apache-2.0 |
| TableFormer accurate | Hugging Face docling-project/docling-models | tableformer_accurate.safetensors | 2a7d6c924b3cd12fb99a09280ca9c33a89c5d60b93253617d2e088c1a40374d9 | CDLA-Permissive-2.0 |
| TableFormer fast | Hugging Face docling-project/docling-models | tableformer_fast.safetensors | 3119563aab5a7c96fda4d621119b63fd8806272b86c30936d15507616422f718 | CDLA-Permissive-2.0 |
| PP-OCRv5_mobile_det | Hugging Face PaddlePaddle/PP-OCRv5_mobile_det | inference.pdiparams | afa1820cb16c1fd0dad589d0f8b389139061c1ef6d68019685fd07be997dda5b | Apache-2.0 |
| PP-OCRv5_mobile_rec | Hugging Face PaddlePaddle/PP-OCRv5_mobile_rec | inference.pdiparams | 2460da90875937c94db97eba74ae3d9e5d4c4c57c42f1f41531c09a26bcc771a | Apache-2.0 |
HTTP workers (127.0.0.1 only, free port chosen by the caller):
HF_HUB_OFFLINE=1 /tmp/toolchains/docling-venv/bin/python tools/rag/docling_worker.py --port PORT --models /tmp/toolchains/docling-models
PADDLE_PDX_CACHE_HOME=/tmp/toolchains/paddleocr-models /tmp/toolchains/paddleocr-venv/bin/python tools/rag/paddleocr_worker.py --port PORT
docling_worker.py implements the POST /v1/convert/source subset of Docling Serve 1.x (sources of kind file, response document.md_content), with Docling OCR off (scans go to PaddleOCR, ocr_applied: false is returned). paddleocr_worker.py implements POST /ocr of PaddleX serving; oneDNN is off (enable_mkldnn=False) because paddlepaddle 3.3.1 CPU raises NotImplementedError (ConvertPirAttribute2RuntimeAttribute) on these models. The adapter adds the extension of the MIME type to the file name sent to Docling, which picks the input format from the extension.
Evidence: services/control-plane/tests/test_rag_docling_ocr_real.py starts both workers and checks, through eai.integrations.docling and eai.integrations.ocr (hence eai.security.egress), an acceptance PDF (text and table), a DOCX with a table (exact cells), ingestion through the API then a query, the refusal of a destination outside the egress policy, and OCR of an image of text rendered with Pillow. Missing toolchain: explicit failure, never skipped.
Sensitive cache and outlier embedding detection (DATA-024, DATA-015)
services/control-plane/tests/test_rag_kit_acceptance.py checks on a real /usr/bin/redis-server (free port) that the RAG cache entry is a Tink ciphertext (eaitink1:), that an entry copied under another tenant's key reads as a miss (unwrapping a DEK of another tenant raises EnvelopeError, never an HTTP 500), and that a Redis outage falls back to the vector store with no content in the logs. The same file quarantines a document whose chunk points away from the document centroid (outlier_embedding). It also covers DATA-019, DATA-020, DATA-022, DATA-023 and scenario 7 (RAG with ACL, authorised citations, lineage from source to answer).
RAG quarantine review
A suspicious document (injection, outlier embedding, untrusted source) is quarantined: its chunks are no longer searchable. Review is reserved to the tenant_admin, security_officer and data_steward roles (REVIEW_ROLES of eai.domains.knowledge), who also need the viewer relation on the knowledge base.
| Operation | Route | Effect |
|---|---|---|
| List the quarantine | GET /v1/knowledge-bases/{kb_id}/quarantine | Documents waiting for review |
| Decide | POST /v1/knowledge-bases/{kb_id}/documents/{doc_id}/review | release: chunks searchable again; reject: document rejected; decision audited |
Before ingestion: file security
No byte reaches Docling, OCR or RAG without a scan released by ClamAV and YARA-X: see file-security-pipeline.md.
Related pages
- Retention, erasure, DLP, tokenization and consents: retention-privacy.md.
- Tenant encryption keys and rotation: secrets-vault.md.
- Document access control (OpenFGA relations, clearance): authentication-authorization.md.