The central security invariant is:
Authorization must happen before vector ranking, keyword matching, neighbor expansion, reranking, prompt construction, and LLM generation.
The model should never receive unauthorized context.
This is the difference between secure RAG and a RAG system that merely asks the model to behave.
AccessAware RAG currently uses a server-side TrustedPrincipal as the authorization boundary for endpoints and retrieval.
Current demo identity flow:
X-User-Email
-> get_current_user()
-> get_current_principal()
-> resolve_principal_from_user()
-> TrustedPrincipal
-> endpoint and retrieval authorization
Important security properties:
- Endpoint authorization should depend on
TrustedPrincipal, not directly on rawX-User-Email. - Demo header authentication is accepted only when
AUTH_MODE=demoandDEMO_AUTH_ENABLED=true. - Non-demo auth modes must not silently fall back to demo header authentication.
- Document listing, document detail, cleanup, upload/delete controls, audit access, vector ask, and RAG ask use
TrustedPrincipal. - Document detail access enforces both classification and grants, preventing direct-ID access to grant-protected documents.
- Authorized retrieval applies classification and grant filters before vector ranking, keyword candidate selection, reranking, prompt construction, and model context construction.
- The model should never receive unauthorized chunks.
Current proof points:
- Unit tests cover principal construction, clearance ordering, access-control helpers, demo auth guard behavior, and retrieval authorization ordering.
- Smoke tests verify document visibility and RAG behavior across public, internal, restricted, and confidential users.
- Rich-demo evals verify allowed paths, denied paths, and unauthorized high-similarity questions for benefits, Northwind, and executive-risk documents.
| Risk | Description | Control |
|---|---|---|
| Cross-user data leakage | A user asks a question and semantically similar restricted documents are retrieved. | Apply authorization before retrieval and ranking. |
| Prompt injection | Retrieved text tries to override instructions or reveal data. | Retrieve only authorized context; require grounded answers. |
| Over-broad clearance | A user has clearance but no business need for a document. | Add group/document grants as a need-to-know layer. |
| Sensitive audit logs | Logs accidentally store prompts, answers, chunks, PHI, or secrets. | Log safe metadata only. |
| External provider exposure | Authorized sensitive chunks are sent to an external model. | Provider-policy metadata, decision service, upload-time embedding gate, RAG answer-generation gate, observe/enforce modes, and audit; future routing and private/local model options. |
Access is based on:
- user identity
- user clearance level
- document classification
- user group membership
- document-level grants
Classification ladder:
public -> internal -> restricted -> confidential
A user's clearance allows access to that level and lower levels:
public user -> public
internal user -> public, internal
restricted user -> public, internal, restricted
confidential user -> public, internal, restricted, confidential
Classification alone is not enough.
A restricted user should not automatically see every restricted document. They should only see restricted documents they have a business reason to access.
Current grant rule:
User can access a document if:
1. user clearance allows the document classification
AND
2. either:
- the document has no grants
- the document is directly granted to the user
- the document is granted to one of the user's groups
- the user is a confidential prototype admin
Important design decision:
Grants do not bypass classification.
A grant is not a magic allow. If an internal user is granted to a confidential document, classification should still deny access.
Current prototype rule:
no grants = open within classification
grants exist = restricted audience within classification
This keeps simple demo documents usable without requiring every document to have grants.
For stricter production environments, the rule can become:
every non-public document requires an explicit grant
Current prototype behavior:
confidential clearance = view-all demo admin
This is useful for local demos and smoke tests.
Production should separate:
- clearance level
- admin role
- break-glass access
- audit reviewer role
In production, a confidential user should not automatically bypass need-to-know grants.
Correct order:
resolve demo/local identity when enabled
-> build TrustedPrincipal
-> derive allowed classifications from TrustedPrincipal
-> use TrustedPrincipal group memberships
-> filter authorized documents/chunks
-> perform vector ranking over authorized candidates only
-> add authorized keyword candidates only
-> expand neighbors inside authorized documents
-> rerank authorized chunks
-> build context from authorized chunks only
-> evaluate provider/data-egress policy for answer_generation
-> send authorized context to model only when policy mode allows it
-> audit safe metadata, including provider-policy decision fields
Unsafe order:
retrieve top vector matches from all documents
-> send to model
-> ask model not to reveal unauthorized content
The unsafe order gives the model access to data the user should not see.
Neighbor expansion improves answer quality by adding adjacent chunks from the same document.
Security rule:
seed chunk must be authorized
neighbor chunks must belong to the same authorized document
Neighbor expansion must not jump into unauthorized documents.
Reranking should operate only on authorized candidates.
Current model:
authorized vector candidates
+ authorized keyword candidates
+ authorized neighboring chunks
-> heuristic reranking
Future model-based rerankers must follow provider policy. Sensitive or PHI-containing chunks should not be sent to an external reranking service unless explicitly allowed.
Audit logging proves that the system enforced authorization and operated safely.
The goal is not to store everything. The goal is to record enough metadata to understand what happened without turning logs into a sensitive data store.
Useful audit metadata:
| Metadata | Why it helps |
|---|---|
event_type |
Classifies the action. |
user_email / user_id |
Ties event to user. |
route |
Shows which endpoint was used. |
status |
Success, denied, or error. |
result_count |
Shows retrieval/listing size. |
source_filter |
Shows requested corpus/source. |
allowed_classifications |
Shows classification scope. |
source_document_ids |
Shows contributing docs without logging content. |
source_classifications |
Confirms classification boundaries. |
top_chunk_ids |
Supports debugging without chunk text. |
provider / embedding_model / answer_model |
Tracks model/provider usage. |
provider_policy_mode |
Shows whether provider policy ran in off, observe, or enforce mode. |
ai_provider_mode |
Shows the configured provider category such as external, enterprise, or local. |
provider_policy_operation |
Shows the operation being evaluated, such as embedding or answer generation. |
provider_policy_allowed |
Shows whether the retrieved authorized context would be allowed for that provider/operation. |
provider_policy_reasons |
Shows policy reasons such as confidential_external_ai_blocked. |
provider_policy_document_ids |
Identifies affected documents without logging content. |
retrieval_limit |
Shows context-size control. |
access_control |
Confirms classification/grants mode. |
grant_filter_mode |
Shows enforced vs prototype bypass behavior. |
denied_reason |
Supports security review. |
Avoid logging:
- full questions when sensitive policy says not to
- generated answers
- retrieved chunk text
- full document text
- embeddings
- PHI/PII
- secrets
- confidential raw details
Implemented audit event categories include:
- document upload succeeded
- vector question asked
- RAG question asked
- cleanup blocked/succeeded
- access denied
- audit events access denied
- audit events listed / retrieved
Potential future event categories:
- document listed
- document detail accessed
- grant changed
- provider policy enforced block
- redaction applied
- model-call policy decision
The audit listing endpoint is confidential/admin-only in the prototype.
Example usage:
GET /audit-events?limit=20
GET /audit-events?event_type=RAG_QUESTION_ASKED&limit=10
Rules:
- require
X-User-Email - allow confidential/admin user only
- return newest events first
- support safe filters such as
limitandevent_type - do not expose anything more sensitive than the audit rows already contain
Access control answers:
Who is allowed to retrieve this content?
Provider policy answers:
Where is this content allowed to be processed?
For regulated, PHI-capable, or highly sensitive use, provider calls are data egress events. This includes embedding, reranking, and answer generation.
Current implemented foundation:
| Control | Status |
|---|---|
contains_phi document flag |
Implemented. |
external_ai_approved document flag |
Implemented. |
requires_local_processing document flag |
Implemented. |
| Provider modes: external, enterprise, local | Implemented in policy service. |
| Operations: embedding, answer generation, reranking | Implemented in policy service. |
| Provider-policy tests | Implemented. |
| Upload/list/detail provider metadata | Implemented. |
| Upload audit metadata | Implemented. |
| RAG observe-mode provider-policy audit | Implemented. |
| Upload-time embedding enforcement | Implemented. |
| RAG answer-generation enforcement | Implemented. |
| Provider routing | Future work. |
| Local/private model path | Future work. |
Current provider-policy flows:
upload-time embedding
-> evaluate provider policy from declared document metadata with operation=embedding
-> observe mode audits and continues
-> enforce mode blocks before file read, extraction, chunking, embedding, or storage
RAG answer generation
-> authorized retrieval
-> dedupe source documents
-> evaluate provider policy for configured ai_provider_mode and operation=answer_generation
-> observe mode audits and answers normally
-> enforce mode returns safe fallback without calling the answer model
Observed confidential example:
provider_policy_mode=observe
ai_provider_mode=external
provider_policy_operation=answer_generation
provider_policy_allowed=false
provider_policy_reasons=confidential_external_ai_blocked
provider_policy_document_ids=27
Important distinction:
A user can be authorized to retrieve a document while the document is still not approved for external AI processing.
Future production work should add:
- provider routing for
external|enterprise|local - local/private embedding and answer-generation implementations
- reranker provider policy checks
- local/private model routing
- retrieval-only mode
- redaction/de-identification before model calls when required
- explicit provider-policy blocked audit events
Even with upload-time embedding and answer-generation enforcement, the project should not be described as PHI-ready until local/private providers, redaction/de-identification, production authentication, and compliance validation exist.
Current limitations to avoid overclaiming:
- prototype identity via gated
X-User-Emaildemo auth, not real JWT/OIDC auth - confidential user behaves as demo admin
- no full production-grade ReBAC/Zanzibar model yet
- provider policy is enforced for upload-time embedding and RAG answer generation, but provider routing is not fully implemented
- not PHI-ready until local/private model paths, redaction/de-identification, production auth, and compliance validation exist
- audit safety depends on discipline when adding new event details