# ACL Audit: Comprehensive Cross-Service Analysis
Document Purpose: Track ACL compliance status across all Python services. Methods are categorized by: - Service name and file location - Operation type (READ, WRITE, CREATE, DELETE, QUERY, ADMIN) - Permission required (READ, WRITE/EDIT, ADD, DELETE, etc.) - ACL check implementation status - effective_user_id auto-default status
Last Updated: 2026-07-09
Scope: All microservices in PythonServer/src/services/
Column Definitions
Type
- READ — Query/retrieval operation; returns data without modification
- WRITE/EDIT — Modifies existing resource; requires EDIT permission
- CREATE — Creates new resource; requires ADD permission (or similar)
- DELETE — Removes resource; requires DELETE permission
- QUERY — Complex search/analysis; may combine multiple operations
- CHECK — Permission verification only (no data modification)
- ADMIN — Administrative/maintenance operation; N/A for ACL
Permission
- READ — View/query data
- EDIT — Modify existing resource
- ADD — Create/add child resource
- DELETE — Remove resource
- N/A — No entity-level permission needed
ACL Status
- ✅ CHECKED — ACL permission check is implemented and enforced
- ⚠️ PARTIAL — ACL check exists but incomplete (e.g., only checks if effective_user_id is provided)
- ❌ MISSING — No ACL check exists; unrestricted access
- ✅ N/A — No ACL check needed (utility/admin/internal operation)
- ⚠️ DELEGATED — Caller is responsible for pre-filtering by ACL
effective_user_id Default
- ✅ OK — Auto-defaults to
get_user_service().get_user_uuid()when None - ❌ TODO — Has
effective_user_idparameter but NO auto-default - ⚠️ REQUIRED — effective_user_id is mandatory (no default possible)
- N/A — No effective_user_id parameter (not applicable or computed differently)
- BYPASS — Explicit None-check returns True (system bypass)
Auto-Default Pattern:
if effective_user_id is None:
user_service: UserInterface = get_user_service()
effective_user_id = user_service.get_user_uuid()
DocumentsService
File: PythonServer/src/services/documents_service.py
Methods: 61 total public; 54 ACL-relevant documented (7 are internal utility/admin)
DocumentsService: All Operations
| Method | Type | Target | Permission | ACL Status | effective_user_id Default | Notes |
|---|---|---|---|---|---|---|
get_all_documents() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_document() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_documents() |
READ | document (batch) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_documents_by_status() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_documents_by_type() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_documents_count() |
READ | document (count) | READ | ✅ CHECKED | ✅ OK | Returns count of accessible documents; has auto-default |
get_document_id_by() |
READ | document | N/A | ✅ N/A | N/A | URL-based query; no entity-level ACL needed |
get_document_id_by_name() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_document_categories() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
document_has_any_categories() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
count_document_categories() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_chapter_for_editing() |
READ | document → chapter | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_document_for_editing() |
READ | document | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_chapter_ids_for_document() |
READ | document → chapters | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
find_ontology_for_document() |
READ | document → ontology | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_category_ontology_id_for_document() |
READ | document → ontology | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_documents_with_multiple_categories() |
READ | dossier → documents | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_documents_without_category() |
READ | dossier → documents | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_document_ids_for_dossier() |
READ | dossier → document IDs | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_fragments_by_ids() |
READ | fragments (batch) | READ | ✅ CHECKED | ✅ OK | Verifies parent document READ access; has auto-default |
get_nodes_from_fragments() |
READ | fragments (batch) | READ | ✅ CHECKED | ✅ OK | Verifies parent document READ access; has auto-default |
get_chapters_for_documents() |
READ | document → chapters | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
get_chapter_counts_for_documents() |
READ | document → chapter count | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
search_documents_by_semantic() |
READ | documents (filter) | N/A | ⚠️ DELEGATED | N/A | Pure filtering function; caller responsible for ACL |
get_fragment_type() |
READ | fragment | N/A | ✅ N/A | N/A | Utility parsing function; no ACL needed |
create_document() |
CREATE | document | ADD | ⚠️ PARTIAL | ✅ OK | Allows creation; ACL enforcement delegated to link_document_to_dossier() when linking to dossier |
create_or_update_document_by_url() |
CREATE | document | ADD | \u26a0\ufe0f PARTIAL | \u2705 OK | effective_user_id: Optional[str] = None; MERGE-based create/update; actual linking will check ADD permission on dossier |
update_document() |
WRITE | document | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns error on denial |
delete_document() |
DELETE | document | DELETE | ✅ CHECKED | ✅ OK | Verifies DELETE permission on document; returns error on denial |
update_document_status() |
WRITE | document | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns error on denial |
remove_document_category() |
WRITE | document → category | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns error on denial |
remove_all_document_categories() |
WRITE | document → categories | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; effective_user_id parameter added with auto-default |
upsert_document_category() |
WRITE | document → category | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns error on denial |
upsert_document_categories_batch() |
WRITE | document → categories (batch) | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT on first doc; passes effective_user_id to each upsert_document_category call |
update_document_file() |
WRITE | document | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns None on denial |
write_document_file() |
WRITE | document | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns None on denial |
update_document_configuration() |
WRITE | document | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns error on denial |
update_document_categories() |
WRITE | document → categories | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns error on denial |
update_document_content() |
WRITE | document | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on document; returns error on denial |
create_or_update_document_summary() |
WRITE | document → summary | EDIT | ⚠️ PARTIAL | ✅ OK | Retrieves documentid from the document node; ACL inherited from document operations |
delete_document_nodes_for_reingestion() |
DELETE | document | DELETE | ✅ CHECKED | ✅ OK | Verifies DELETE permission on document; returns error on denial |
create_or_update_chapter() |
CREATE | document → chapter | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on parent document; returns error on denial |
create_or_update_image() |
CREATE | document → chapter → image | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on parent document; returns error on denial |
create_or_update_keyword() |
CREATE | document → keyword | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on parent document; returns error on denial |
create_or_update_summary() |
CREATE | chapter → summary | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on parent document via document_id in summary_data |
create_or_update_paragraph() |
CREATE | chapter → paragraph | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT permission on parent document via document_id in paragraph_data |
create_or_update_paragraphs_batch() |
CREATE | chapter → paragraphs (batch) | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT on first paragraph's document; delegates to single-paragraph method |
create_or_update_questions_batch() |
CREATE | chapter → questions (batch) | EDIT | ✅ CHECKED | ✅ OK | Verifies EDIT on first question's document; delegates to single-question method |
save_enhanced_content() |
WRITE | content | EDIT | ⚠️ PARTIAL | ✅ OK | Retrieves documentid from node; versions saved without explicit ACL check (internal operation) |
delete_content_versions() |
DELETE | content versions | DELETE | ⚠️ DELEGATED | ✅ OK | Caller responsible for verifying parent document permissions before deleting versions |
validate_category_key() |
ADMIN | ontology | N/A | ✅ N/A | N/A | Validates structure; no entity-level ACL needed |
validate_category_value() |
ADMIN | ontology | N/A | ✅ N/A | N/A | Validates structure; no entity-level ACL needed |
generate_new_document_id() |
ADMIN | N/A | N/A | ✅ N/A | N/A | Generates ID only; no ACL needed |
create_vector_index() |
ADMIN | index | N/A | ✅ N/A | N/A | Index management; admin operation |
create_all_required_indexes() |
ADMIN | indexes | N/A | ✅ N/A | N/A | Index management; admin operation |
create_fulltext_index() |
ADMIN | index | N/A | ✅ N/A | N/A | Index management; admin operation |
cleanup_orphan_nodes() |
ADMIN | orphans | N/A | ✅ N/A | N/A | Maintenance; no entity-level ACL needed |
delete_orphan_nodes() |
ADMIN | orphans | N/A | ✅ N/A | N/A | Maintenance; no entity-level ACL needed |
delete_orphan_document_nodes() |
ADMIN | orphans | N/A | ✅ N/A | N/A | Maintenance; no entity-level ACL needed |
delete_all_indexes_and_clear_database() |
ADMIN | all | N/A | ✅ N/A | N/A | Admin operation; clears entire database |
delete_temporary_documents() |
DELETE | documents | DELETE | ⚠️ PARTIAL | N/A | Deletes by session ID; session verification incomplete |
DossiersService
File: PythonServer/src/services/dossiers_service.py
Methods: 12 total (9 READ/QUERY, 1 CREATE, 1 DELETE, 1 helper)
| Method | Type | Target | Permission | ACL Status | effective_user_id Default | Notes |
|---|---|---|---|---|---|---|
_check_dossier_permission() |
CHECK | dossier | (varies) | ✅ CHECKED | N/A | Helper method; checks specific permission via Cypher; returns bool |
get_all_dossiers() |
READ | dossier (all) | READ | ✅ CHECKED | ✅ OK | Uses acl_dossier_where_clause(); auto-default implemented |
get_dossier() |
READ | dossier | READ | ✅ CHECKED | ✅ OK | Calls _check_dossier_permission() when effective_user_id provided |
get_ontology_id_for_dossier() |
READ | dossier → ontology | READ | ✅ CHECKED | ✅ OK | Verifies dossier READ permission |
get_documents_of_dossier() |
READ | dossier → documents | READ | ✅ CHECKED | ✅ OK | Checks dossier READ via _check_dossier_permission() |
link_document_to_dossier() |
WRITE | dossier ← document | ADD | ✅ CHECKED | ✅ OK | Requires ADD permission on dossier; auto-default implemented |
get_dossiers_for_document() |
READ | document → dossiers | READ | ✅ CHECKED | ✅ OK | Uses acl_dossier_where_clause() for filtering |
count_documents_in_dossier() |
READ | dossier → document count | READ | ✅ CHECKED | ✅ OK | Verifies dossier READ, returns 0 on denial |
create_dossier() |
CREATE | dossier | ADD | ✅ CHECKED | ✅ OK | Verifies ADD permission on user's personal dossier; returns None on denial |
delete_dossier() |
DELETE | dossier | DELETE | ✅ CHECKED | ✅ OK | Requires DELETE permission; auto-default implemented |
get_category_usage_stats_in_dossier() |
READ | dossier → category stats | READ | ✅ CHECKED | ✅ OK | Verifies dossier READ permission |
get_user_dossier_id() |
READ | user → dossier | N/A | ✅ N/A | N/A | System operation; returns user's personal dossier |
OntologyService
File: PythonServer/src/services/ontology_service.py
Methods: 6 total (3 CHECK, 2 READ, 1 helper)
| Method | Type | Target | Permission | ACL Status | effective_user_id Default | Notes |
|---|---|---|---|---|---|---|
check_read_access_on_ontology() |
CHECK | ontology | READ | ✅ CHECKED | BYPASS | Returns True if effective_user_id=None (system bypass); mirrors C# AclService |
check_read_access_on_community() |
CHECK | community | READ | ✅ CHECKED | BYPASS | Delegates to check_read_access_on_ontology(); returns True if None |
check_has_any_read_access() |
CHECK | ontology (any) | READ | ✅ CHECKED | BYPASS | Fast guard for tenant-admin check; returns True if None |
get_entities_by_ids() |
READ | entity (batch) | READ | ⚠️ PARTIAL | ✅ OK | Has effective_user_id parameter; auto-default implemented; ACL filtering ready |
categories_query() |
QUERY | category | READ | ⚠️ PARTIAL | ✅ OK | Has effective_user_id parameter; auto-default implemented; category search |
has_ontologies() |
READ | ontology | N/A | ✅ N/A | N/A | Helper method; checks if ontologies exist; no ACL needed |
RagService
File: PythonServer/src/services/rag_service.py
Methods: 13 total (9 QUERY/SEARCH, 1 batch-retry helper, 1 main API, 1 analyzer, 1 query planner)
| Method | Type | Target | Permission | ACL Status | effective_user_id Default | Notes |
|---|---|---|---|---|---|---|
_semantic_search() |
QUERY | document (search) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
_semantic_search_with_acl() |
QUERY | document (batch-retry) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented; target_count now has default value |
_semantic_search_partitioned() |
QUERY | document (search) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
_semantic_search_multilang() |
QUERY | document (search) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
_get_semantic_chapters() |
QUERY | chapter (fragments) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
_get_semantic_paragraphs() |
QUERY | paragraph (fragments) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
_get_semantic_summaries() |
QUERY | summary (fragments) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
_text_search() |
QUERY | document (text) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
_text_search_constrained() |
QUERY | document (text, subset) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
_fetch_all_document_ids_for_search() |
READ | document ID (all) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented; ACL-filtered population |
_evaluate_thalabus_ast() |
QUERY | document (complex) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
thalabus_query() |
QUERY | document (user query) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented; signature changed to effective_user_id: Optional[str] = None |
thalabus_query_analyser() |
QUERY | document (analysis) | READ | ✅ CHECKED | ✅ OK | Phase 2: auto-default implemented |
Summary Statistics
By Service
| Service | Total | READ | WRITE/CREATE/DELETE | ADMIN/CHECK | N/A | ✅ CHECKED | ❌ MISSING | ⚠️ PARTIAL | ✅ OK (auto-default) | ❌ TODO (auto-default) | ⚠️ REQUIRED |
|---|---|---|---|---|---|---|---|---|---|---|---|
| DocumentsService | 54 | 24 | 29 | 10 | 1 | 46 | 0 | 3 | 53 | 0 | 0 |
| DossiersService | 12 | 9 | 2 | 1 | 0 | 12 | 0 | 0 | 12 | 0 | 0 |
| OntologyService | 6 | 2 | 0 | 3 | 1 | 5 | 0 | 1 | 5 | 0 | 0 |
| RagService | 13 | 1 | 0 | 0 | 12 | 13 | 0 | 0 | 13 | 0 | 0 |
| TOTALS | 85 | 36 | 31 | 14 | 14 | 76 | 0 | 4 | 83 | 0 | 0 |
By Status
✅ CHECKED (54 methods): - READ methods: 36 (all documented) - WRITE/CREATE/DELETE: 2 (link_document, delete_dossier) - CHECK methods: 11 (permission verification helpers) - QUERY/REQUIRED: 5 (mandatory effective_user_id methods in RagService)
❌ MISSING (29 methods - CRITICAL SECURITY GAP):
- All WRITE/CREATE/DELETE operations in DocumentsService (28 methods)
- create_dossier() in DossiersService (1 method)
⚠️ PARTIAL (2 methods):
- get_entities_by_ids() - Has effective_user_id but no auto-default
- categories_query() - Has effective_user_id but no auto-default
effective_user_id Auto-Default Status: - ✅ OK: 42 methods (auto-defaults fully implemented - COMPLETE) - ❌ TODO: 0 methods (no remaining work) - ⚠️ REQUIRED: 3 methods (effective_user_id is mandatory, no default needed) - BYPASS: 3 methods (explicit None return True) - N/A: 14 methods (no effective_user_id parameter)
Remediation Priorities
Priority 1: CRITICAL - Document Write Operations (28 methods)
Risk: Any effective user with method access can modify/delete ANY document without permission checks.
Required: Add effective_user_id parameter + enforce EDIT permission check before ANY modification.
Methods:
- Document-level: update_document, delete_document, update_document_status, remove_document_category, remove_all_document_categories, upsert_document_category, upsert_document_categories_batch, update_document_file, write_document_file, update_document_configuration, update_document_categories, update_document_content, create_or_update_document_summary, delete_document_nodes_for_reingestion (14 methods)
- Nested content: create_or_update_chapter, create_or_update_image, create_or_update_keyword, create_or_update_summary, create_or_update_paragraph, create_or_update_paragraphs_batch, create_or_update_questions_batch, save_enhanced_content, delete_content_versions (9 methods)
- Document creation: create_document, create_or_update_document_by_url (2 methods)
- Dossier: create_dossier (1 method)
Priority 2: HIGH - effective_user_id Auto-Defaults (42 methods) - ✅ COMPLETE
Status: ✅ All 42 methods now have automatic user context detection implemented.
Completed: Auto-default pattern added to all 42 methods where effective_user_id has optional default.
Services: - DocumentsService: 22 methods ✅ - DossiersService: 9 methods ✅ - OntologyService: 2 methods ✅ - RagService: 9 methods ✅
Priority 3: MEDIUM - Partial/Incomplete ACL (3 methods)
Status: ACL checks exist but incomplete or missing auto-default.
Methods:
- get_entities_by_ids() - Add auto-default
- categories_query() - Add auto-default
- delete_temporary_documents() - Enhance session verification
Implementation Pattern Reference
For READ Methods (with auto-default):
def method_name(self, param: str, ..., effective_user_id: Optional[str] = None) -> ReturnType:
"""Docstring..."""
if effective_user_id is None:
user_service: UserInterface = get_user_service()
effective_user_id = user_service.get_user_uuid()
if effective_user_id:
# Verify READ permission
resource = self.get_resource(resource_id, effective_user_id=effective_user_id)
if resource is None:
log(WARNING, f"Method: READ access denied for effective user '{effective_user_id}' on resource '{resource_id}'")
return empty_response
# Proceed with query
return result
For WRITE Methods (permission check + auto-default):
def update_something(self, resource_id: str, data: Dict, effective_user_id: Optional[str] = None) -> bool:
"""Docstring..."""
if effective_user_id is None:
user_service: UserInterface = get_user_service()
effective_user_id = user_service.get_user_uuid()
if effective_user_id:
# Verify EDIT permission
if not self.check_edit_permission(effective_user_id, resource_id):
log(WARNING, f"EDIT access denied for effective user '{effective_user_id}' on resource '{resource_id}'")
return False
# Proceed with modification
return True
Notes
-
Bypass behavior: When
effective_user_id=Noneis passed intentionally (system/admin operations), no ACL checks should apply. The auto-default ensures user-triggered calls always have context. -
Query pattern consistency: All ACL-embedded Cypher queries should use centralized fragments from
acl_cypher_utils.pyto avoid duplication and maintain consistency. -
Nested content: Chapters, paragraphs, summaries inherit permissions from parent Document. Modifications must check document access first.
-
Tenant isolation: All queries must respect tenant boundaries. No cross-tenant access is permitted.
-
Soft delete: Resources with
isDeleted=truemust be excluded from all results, regardless of ACL status.