GateKeep RAG is an enterprise-grade, permission-aware multi-tenant Retrieval-Augmented Generation (RAG) platform built with FastAPI, PostgreSQL + Alembic, Qdrant vector database, local sentence-transformer embeddings, and local/remote LLM support (MockLLM / Ollama llama3.2:3b).
Every document chunk carries tenant ownership and role/clearance access control lists (ACLs). Search is pre-filtered directly inside Qdrant before vector similarity search, post-verified against PostgreSQL before prompt compilation, and cryptographically recorded in an append-only, hash-chained audit ledger.
flowchart TD
Client["Client / Frontend"] -->|"1. JWT Bearer Token + Query"| API["FastAPI Gateway"]
API -->|"2. Verify Signature & Resolve Identity"| Auth["Auth Dependency<br/>(scrypt / HMAC-SHA256)"]
Auth -->|"3. Principal Context<br/>(tenant_id, roles, clearance)"| Retriever["TenantScopedRetriever"]
subgraph VectorSearch ["Stage 1: Pre-Filtered Vector Search"]
Retriever -->|"4. build_filter(principal)<br/>Additive status=ready"| Qdrant["Qdrant Vector DB<br/>(Cosine Similarity, 384d)"]
Qdrant -->|"5. Candidate Chunks"| Retriever
end
subgraph DefenseInDepth ["Stage 2: Defense-in-Depth Verification"]
Retriever -->|"6. can_access(principal, acl)<br/>Document status == ready"| Postgres["PostgreSQL 16<br/>(chunks, documents, roles)"]
Postgres -.->|"Mismatch Alert"| SecurityAlert["Audit: security_alert"]
Postgres -->|"7. Verified Chunks Only"| PromptBuilder["Grounded Prompt Builder"]
end
subgraph Generation ["Stage 3: Grounded Synthesis & Guardrails"]
PromptBuilder -->|"8. Grounded Context"| LLM["LLM Provider<br/>(MockLLM / Ollama llama3.2:3b)"]
LLM -->|"9. Raw Output"| OutputGuard["OutputGuard<br/>(Citation Scrubber)"]
OutputGuard -->|"10. Guarded Answer"| AuditService["Audit Service<br/>(pg_advisory_xact_lock)"]
end
subgraph AuditLog ["Stage 4: Tamper-Evident Hash Chain"]
AuditService -->|"11. SHA-256 Hash-Chained Row"| AuditTable["PostgreSQL audit_logs<br/>(Non-owner gatekeep_app role)"]
AuditTable -->|"12. Final Response with Citations & audit_id"| Client
end
+-----------------------------------------------------------------------------------------+
| GATEKEEP / ACCESS-AWARE RAG |
| One question. Different truth. |
+------------------------------------+----------------------------------------------------+
| Persona: Bob (Acme HR) | Persona: Dave (Acme Employee) |
| Query: "Engineer salary bands?" | Query: "Engineer salary bands?" |
| | |
| Answer: | Answer: |
| The salary band for engineers is | "I don't have access to information |
| 145,000 base pay with stock. | that answers this." |
| | |
| Citations: [salary-bands-2026] | Citations: [] (Standard no-results response) |
+------------------------------------+----------------------------------------------------+
(Demo GIF placeholder: see docs/DEMO.md for live CLI and React demo walkthrough)
All security properties are verified against live PostgreSQL 16 and Qdrant 1.19.1 backends on an isolated multi-tenant corporate corpus:
Every-Principal Counterfactual Invariance with True Removal (Corpus size: 210 chunks across 3 tenants)
citations: [], identical refusal answer).Canary Token Defense (Corpus size: 210 chunks across 3 tenants)
CANARY_<HEX16>_<TENANT>_<DOC>_C<IDX>, 128 bits of CSPRNG entropy via Python secrets.token_hex(16)) was seeded into all 210 chunks of the corpus (Corpus size: 210 chunks).PromptCapturingLLM)Threshold-Independence at Threshold 0.00 (Corpus size: 210 chunks across 3 tenants)
0.00 (allowing every query to retrieve the top-5 permitted chunks regardless of similarity score; Corpus size: 210 chunks), cross-tenant leak rate remains 0.00% (0 leaks across 4,972 pairs) and canary violations remain 0 / 823,996 checks (verified in tests/security/test_threshold_independence.py). Access control operates at the vector pre-filter and relational verification layers, independent of the similarity score threshold.Role-Condition Mutation Testing (Corpus size: 210 chunks across 3 tenants)
build_filter(principal) in src/app/rag/vectorstore/tenant_scoped_retriever.py, the live counterfactual test immediately fails with real divergence assertions showing unauthorized chunks retrieved into World 1 candidate sets.Cryptographic Audit Hash Chain (Corpus size: 210 chunks across 3 tenants)
pg_advisory_xact_lock) and dedicated non-owner role privileges (gatekeep_app). Verification is performed via /v1/audit/verify.Non-Owner Database Role & Least Privilege
gatekeep_app database role, which possesses SELECT, INSERT on app tables, and row-level trigger constraints preventing UPDATE or DELETE on audit_logs. Database migrations and administrative tasks execute under a separate owner role.The evaluation corpus is small and synthetic (210 chunks across 3 tenants, 30 documents). Hand-written queries were written by an AI that had access to the corpus. Empirical testing indicates that the dense-retrieval-plus-threshold setup cannot reliably reject unanswerable questions on this corpus (at similarity threshold 0.35, 88.89% of unanswerable queries return permitted chunks with weak semantic overlap instead of being rejected). Retrieval quality is a separate concern from the security properties and is future work (hybrid search combining candidate-scoped BM25 with dense embeddings, and a larger blind human-written query set).
[!WARNING] Demo-Only Credentials & Secrets Replacement: All personas below (
alice/alice,bob/bob, etc.) and default service credentials (such asgatekeep:gatekeepand default JWT secrets) are seeded strictly for local sandbox demonstration, test suites, and offline evaluation. In any staging or production deployment, default credentials and static database passwords must be replaced by strong, dynamically provisioned secrets managed via a dedicated secrets store (such as AWS Secrets Manager or HashiCorp Vault).
[!IMPORTANT] Password Hash Migration & Reset Notice: The password hashing format has been upgraded to explicitly encode scrypt parameters as
scrypt$<n>$<r>$<p>$<salt_b64>$<digest_b64>(e.g.scrypt$131072$8$1$...) with strict parameter caps ($N \le 2^{18}$, $r \le 16$, $p \le 4$), and blind parameter fallbacks have been eliminated. Any pre-existing user accounts created under earlier unparameterized hash formats cannot be verified and require an administrative password reset or re-seeding viapython scripts/seed_demo.py.
| Username | Password | Tenant | Roles | Clearance | Description |
|---|---|---|---|---|---|
alice | alice | acme-corp | admin | restricted | Acme Tenant Administrator |
bob | bob | acme-corp | hr | restricted | Acme HR Specialist (can see salaries) |
carol | carol | acme-corp | finance | confidential | Acme Finance Analyst |
dave | dave | acme-corp | employee | internal | Acme General Employee (no salary access) |
frank | frank | globex-inc | admin | restricted | Globex Tenant Administrator |
llama3.2:3b for local generative synthesis# 1. Clone repository
git clone https://github.com/saturn-16/GateKeep-RAG.git
cd GateKeep-RAG
# 2. Configure environment
cp .env.example .env
# Edit .env and supply a secure JWT_SECRET
# 3. Start PostgreSQL 16 and Qdrant 1.19.1
docker compose up -d --build
# 4. Install python dependencies
python -m pip install -e ".[test,security]"
# 5. Run database migrations
alembic upgrade head
# 6. Seed demo dataset (3 tenants, 11 users, vector embeddings)
python scripts/seed_demo.py
$env:RUN_REAL_STACK="1"
$env:PERSISTENCE_BACKEND="postgres"
$env:VECTOR_BACKEND="qdrant"
python -m pytest -v
python scripts/run_eval.py
cd frontend
npm install
npm run dev
Open http://localhost:5173 to explore Ask, Role Comparison, and Audit Explorer.
tenant_id and role conditions (build_filter(principal)). Status condition (status = ready) is strictly additive.can_access) prior to prompt synthesis; unpermitted chunks raise a security_alert.gatekeep_app).For the comprehensive threat model matrix and vulnerability mitigations, see docs/SECURITY.md.
/v1/audit/verify.alice, bob, dave, etc.) and default docker-compose passwords must be replaced with dynamically rotated credentials managed by a dedicated secrets manager in production environments.GateKeep RAG is an enterprise-grade, permission-aware multi-tenant Retrieval-Augmented Generation (RAG) platform built with FastAPI, PostgreSQL + Alembic, Qdrant vector database, local sentence-transformer embeddings, and local/remote LLM support (MockLLM / Ollama llama3.2:3b).
Every document chunk carries tenant ownership and role/clearance access control lists (ACLs). Search is pre-filtered directly inside Qdrant before vector similarity search, post-verified against PostgreSQL before prompt compilation, and cryptographically recorded in an append-only, hash-chained audit ledger.
flowchart TD
Client["Client / Frontend"] -->|"1. JWT Bearer Token + Query"| API["FastAPI Gateway"]
API -->|"2. Verify Signature & Resolve Identity"| Auth["Auth Dependency<br/>(scrypt / HMAC-SHA256)"]
Auth -->|"3. Principal Context<br/>(tenant_id, roles, clearance)"| Retriever["TenantScopedRetriever"]
subgraph VectorSearch ["Stage 1: Pre-Filtered Vector Search"]
Retriever -->|"4. build_filter(principal)<br/>Additive status=ready"| Qdrant["Qdrant Vector DB<br/>(Cosine Similarity, 384d)"]
Qdrant -->|"5. Candidate Chunks"| Retriever
end
subgraph DefenseInDepth ["Stage 2: Defense-in-Depth Verification"]
Retriever -->|"6. can_access(principal, acl)<br/>Document status == ready"| Postgres["PostgreSQL 16<br/>(chunks, documents, roles)"]
Postgres -.->|"Mismatch Alert"| SecurityAlert["Audit: security_alert"]
Postgres -->|"7. Verified Chunks Only"| PromptBuilder["Grounded Prompt Builder"]
end
subgraph Generation ["Stage 3: Grounded Synthesis & Guardrails"]
PromptBuilder -->|"8. Grounded Context"| LLM["LLM Provider<br/>(MockLLM / Ollama llama3.2:3b)"]
LLM -->|"9. Raw Output"| OutputGuard["OutputGuard<br/>(Citation Scrubber)"]
OutputGuard -->|"10. Guarded Answer"| AuditService["Audit Service<br/>(pg_advisory_xact_lock)"]
end
subgraph AuditLog ["Stage 4: Tamper-Evident Hash Chain"]
AuditService -->|"11. SHA-256 Hash-Chained Row"| AuditTable["PostgreSQL audit_logs<br/>(Non-owner gatekeep_app role)"]
AuditTable -->|"12. Final Response with Citations & audit_id"| Client
end
+-----------------------------------------------------------------------------------------+
| GATEKEEP / ACCESS-AWARE RAG |
| One question. Different truth. |
+------------------------------------+----------------------------------------------------+
| Persona: Bob (Acme HR) | Persona: Dave (Acme Employee) |
| Query: "Engineer salary bands?" | Query: "Engineer salary bands?" |
| | |
| Answer: | Answer: |
| The salary band for engineers is | "I don't have access to information |
| 145,000 base pay with stock. | that answers this." |
| | |
| Citations: [salary-bands-2026] | Citations: [] (Standard no-results response) |
+------------------------------------+----------------------------------------------------+
(Demo GIF placeholder: see docs/DEMO.md for live CLI and React demo walkthrough)
All security properties are verified against live PostgreSQL 16 and Qdrant 1.19.1 backends on an isolated multi-tenant corporate corpus:
Every-Principal Counterfactual Invariance with True Removal (Corpus size: 210 chunks across 3 tenants)
citations: [], identical refusal answer).Canary Token Defense (Corpus size: 210 chunks across 3 tenants)
CANARY_<HEX16>_<TENANT>_<DOC>_C<IDX>, 128 bits of CSPRNG entropy via Python secrets.token_hex(16)) was seeded into all 210 chunks of the corpus (Corpus size: 210 chunks).PromptCapturingLLM)Threshold-Independence at Threshold 0.00 (Corpus size: 210 chunks across 3 tenants)
0.00 (allowing every query to retrieve the top-5 permitted chunks regardless of similarity score; Corpus size: 210 chunks), cross-tenant leak rate remains 0.00% (0 leaks across 4,972 pairs) and canary violations remain 0 / 823,996 checks (verified in tests/security/test_threshold_independence.py). Access control operates at the vector pre-filter and relational verification layers, independent of the similarity score threshold.Role-Condition Mutation Testing (Corpus size: 210 chunks across 3 tenants)
build_filter(principal) in src/app/rag/vectorstore/tenant_scoped_retriever.py, the live counterfactual test immediately fails with real divergence assertions showing unauthorized chunks retrieved into World 1 candidate sets.Cryptographic Audit Hash Chain (Corpus size: 210 chunks across 3 tenants)
pg_advisory_xact_lock) and dedicated non-owner role privileges (gatekeep_app). Verification is performed via /v1/audit/verify.Non-Owner Database Role & Least Privilege
gatekeep_app database role, which possesses SELECT, INSERT on app tables, and row-level trigger constraints preventing UPDATE or DELETE on audit_logs. Database migrations and administrative tasks execute under a separate owner role.The evaluation corpus is small and synthetic (210 chunks across 3 tenants, 30 documents). Hand-written queries were written by an AI that had access to the corpus. Empirical testing indicates that the dense-retrieval-plus-threshold setup cannot reliably reject unanswerable questions on this corpus (at similarity threshold 0.35, 88.89% of unanswerable queries return permitted chunks with weak semantic overlap instead of being rejected). Retrieval quality is a separate concern from the security properties and is future work (hybrid search combining candidate-scoped BM25 with dense embeddings, and a larger blind human-written query set).
[!WARNING] Demo-Only Credentials & Secrets Replacement: All personas below (
alice/alice,bob/bob, etc.) and default service credentials (such asgatekeep:gatekeepand default JWT secrets) are seeded strictly for local sandbox demonstration, test suites, and offline evaluation. In any staging or production deployment, default credentials and static database passwords must be replaced by strong, dynamically provisioned secrets managed via a dedicated secrets store (such as AWS Secrets Manager or HashiCorp Vault).
[!IMPORTANT] Password Hash Migration & Reset Notice: The password hashing format has been upgraded to explicitly encode scrypt parameters as
scrypt$<n>$<r>$<p>$<salt_b64>$<digest_b64>(e.g.scrypt$131072$8$1$...) with strict parameter caps ($N \le 2^{18}$, $r \le 16$, $p \le 4$), and blind parameter fallbacks have been eliminated. Any pre-existing user accounts created under earlier unparameterized hash formats cannot be verified and require an administrative password reset or re-seeding viapython scripts/seed_demo.py.
| Username | Password | Tenant | Roles | Clearance | Description |
|---|---|---|---|---|---|
alice | alice | acme-corp | admin | restricted | Acme Tenant Administrator |
bob | bob | acme-corp | hr | restricted | Acme HR Specialist (can see salaries) |
carol | carol | acme-corp | finance | confidential | Acme Finance Analyst |
dave | dave | acme-corp | employee | internal | Acme General Employee (no salary access) |
frank | frank | globex-inc | admin | restricted | Globex Tenant Administrator |
llama3.2:3b for local generative synthesis# 1. Clone repository
git clone https://github.com/saturn-16/GateKeep-RAG.git
cd GateKeep-RAG
# 2. Configure environment
cp .env.example .env
# Edit .env and supply a secure JWT_SECRET
# 3. Start PostgreSQL 16 and Qdrant 1.19.1
docker compose up -d --build
# 4. Install python dependencies
python -m pip install -e ".[test,security]"
# 5. Run database migrations
alembic upgrade head
# 6. Seed demo dataset (3 tenants, 11 users, vector embeddings)
python scripts/seed_demo.py
$env:RUN_REAL_STACK="1"
$env:PERSISTENCE_BACKEND="postgres"
$env:VECTOR_BACKEND="qdrant"
python -m pytest -v
python scripts/run_eval.py
cd frontend
npm install
npm run dev
Open http://localhost:5173 to explore Ask, Role Comparison, and Audit Explorer.
tenant_id and role conditions (build_filter(principal)). Status condition (status = ready) is strictly additive.can_access) prior to prompt synthesis; unpermitted chunks raise a security_alert.gatekeep_app).For the comprehensive threat model matrix and vulnerability mitigations, see docs/SECURITY.md.
/v1/audit/verify.alice, bob, dave, etc.) and default docker-compose passwords must be replaced with dynamically rotated credentials managed by a dedicated secrets manager in production environments.