{"record":{"id":"ce3f197f3de911b8","repo":"zylon-ai/private-gpt","slug":"folder-folder-path-is-not-allowed-for-ingestion","errorCode":null,"errorMessage":"Folder {folder_path} is not allowed for ingestion","messagePattern":"Folder (.+?) is not allowed for ingestion","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/ingest_folder.py","lineNumber":43,"sourceCode":"        self._files_under_root_folder: list[Path] = []\n\n        self.is_local_ingestion_enabled = setting.data.local_ingestion.enabled\n        self.allowed_local_folders = setting.data.local_ingestion.allow_ingest_from\n\n    def _validate_folder(self, folder_path: Path) -> None:\n        if not self.is_local_ingestion_enabled:\n            raise ValueError(\n                \"Local ingestion is disabled.\"\n                \"You can enable it in settings `ingestion.enabled`\"\n            )\n\n        # Allow all folders if wildcard is present\n        if \"*\" in self.allowed_local_folders:\n            return\n\n        for allowed_folder in self.allowed_local_folders:\n            if not folder_path.is_relative_to(allowed_folder):\n                raise ValueError(f\"Folder {folder_path} is not allowed for ingestion\")\n\n    def _find_all_files_in_folder(self, root_path: Path, ignored: list[str]) -> None:\n        \"\"\"Search all files under the root folder recursively.\n\n        Count them at the same time\n        \"\"\"\n        for file_path in root_path.iterdir():\n            if file_path.is_file() and file_path.name not in ignored:\n                self.total_documents += 1\n                self._validate_folder(file_path)\n                self._files_under_root_folder.append(file_path)\n            elif file_path.is_dir() and file_path.name not in ignored:\n                self._find_all_files_in_folder(file_path, ignored)\n\n    def ingest_folder(self, folder_path: Path, ignored: list[str]) -> None:\n        # Count total documents before ingestion\n        self._find_all_files_in_folder(folder_path, ignored)\n        self._ingest_all(self._files_under_root_folder)","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/scripts/ingest_folder.py#L25-L61","documentation":"Raised by LocalIngestWorker._validate_folder when the requested folder is not inside any entry of data.local_ingestion.allow_ingest_from and no '*' wildcard is present. This allowlist constrains the local ingestion script to pre-approved directory trees; a '*' entry disables the restriction entirely. Note the code validates each discovered file path with Path.is_relative_to, so the allowlist entries must be parents of the files being ingested.","triggerScenarios":"allow_ingest_from: [/data/docs] but ingesting /home/user/docs; relative vs absolute path mismatch (entry stored as 'data/docs' while folder_path is resolved absolute); allowlist missing entirely (empty list) so every folder is rejected; trailing-slash or symlinked-path mismatches that break is_relative_to.","commonSituations":"Moving the corpus to a new mount point without updating settings; running the script inside a container where the host path differs from the container path; symlinked data directories whose resolved path escapes the allowlisted prefix; operators enabling ingestion (error 434) but forgetting the second required setting.","solutions":["Add the exact absolute path of the corpus root to data.local_ingestion.allow_ingest_from","Ensure both the allowlist entries and the CLI --folder argument are absolute, consistently resolved paths (no mixing of relative/absolute)","If you trust all paths, add '*' to allow_ingest_from to bypass the check","For containers, align the mounted path with the allowlisted path (mount the corpus at the path listed in settings)"],"exampleFix":"# settings.yaml - before\ndata:\n  local_ingestion:\n    enabled: true\n    allow_ingest_from: [/data/docs]\n\n# CLI ingests /srv/corpus -> rejected\n\n# after\ndata:\n  local_ingestion:\n    enabled: true\n    allow_ingest_from: [/data/docs, /srv/corpus]","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef folder_allowed(folder: Path, allowlist: list[Path]) -> bool:\n    if \"*\" in allowlist:\n        return True\n    folder = folder.resolve()\n    return any(folder.is_relative_to(Path(a).resolve()) for a in allowlist)\n\n# call before starting ingestion","typeGuard":"null","tryCatchPattern":"try:\n    worker.ingest_folder(root, ignored)\nexcept ValueError as e:\n    if \"not allowed for ingestion\" in str(e):\n        raise SystemExit(f\"add {root.resolve()} to data.local_ingestion.allow_ingest_from\")\n    raise","preventionTips":["Always pass absolute, resolved paths on both sides: the CLI --folder and every allow_ingest_from entry","Keep allowlist entries as roots (parents) of the data, not sibling paths","In containers, mount the corpus exactly at the allowlisted path","Remember allow_ingest_from must also be configured - enabling ingestion alone is not enough"],"tags":["ingestion","security","path-validation","configuration"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}