Repository navigation
fix(rag): refuse to index a document whose bytes cannot be read for the cache key - #6978
tharunsridhar wants to merge 7 commits into
Conversation
…he cache key When _content_key cannot read a file it falls back to a path-only <hash>_notfound key. index_document carried on with that key, so a PDF, DOCX, XLSX or PPTX (opened by their own libraries) could be extracted and cached under a key that never changes with the file's content, and every later session served the first version's text. index_document now stops at that point with error_type "file_unreadable" before any cache is read or written. _content_key is unchanged, since is_current() and freshness() read the _notfound key. Test configs that put documents outside the SDK's allowed paths only indexed because of this bug; they now set allowed_paths to their temp dir. Closes amd#4800
|
Verdict: Request changes This PR fixes a real bug: a PDF, Word, Excel or PowerPoint file that GAIA couldn't read for its fingerprint was still indexed, and every later session then served the first version's text. Indexing now stops with a clear "could not read" error. The fix is correct and well tested. It also closes a quiet gap: those four formats were read by their own parsers even outside the folders GAIA is allowed to read. One caller wasn't updated to match:
Real-world evidenceN/A: there is no evidence bundle for this run. The PR says no user surface is touched and reports a manual reproduction with a real PDF. The verdict rests on static review. The tests couldn't run on this runner because pytest isn't installed. 🔍 Technical detailsIssues 🟡 Add a test with a PDF in 🟡 Follow-up: doc drift ( Sibling paths checked
Strengths
|
index_document now refuses a document it cannot read for the cache key, and without allowed_paths the SDK only reads the working directory. quick_rag() would therefore reject a PDF, DOCX, XLSX or PPTX anywhere else, which worked before only because the parser opened the path itself. Grant the named file when the caller did not scope the SDK, as AgentSDK._index_named_document does. An explicit allowed_paths is left alone. Refs amd#4800
|
Thanks for closing the fingerprint gap. This now refuses readable PDFs in two
🔍 Technical details
|
index_document refuses a file outside the working folder unless it was granted, so the chat's /index command rejected a readable PDF given by absolute path. Grant the named file first, as _index_documents and quick_rag do. A folder named to /index grants the files it holds. /resume ignored the index result and still added the file to indexed_files, so the chat reported documents as loaded that were not. Check success before adding, log why a document was skipped, and report how many of the session's documents are actually loaded. Resume does not grant paths: a saved session file must not widen the read scope. docs/sdk/sdks/rag.mdx no longer says RAG can index any readable file without allowed_paths. Refs amd#4800
|
Thanks — Optional hardening while you're there: |
Granting each file in a folder resolves symlinks and skips the protected-file check that the agent's index_directory tool applies. A link inside the folder could add its target to the session's file scope, and a file such as credentials.json would be indexed. Skip both in the folder loop of /index. Naming a single file is still the user's explicit grant. Refs amd#4800
|
Thanks @kovtcharov-amd. The |
…amd#7001) The `Verify external URLs` job in Check Documentation Links fails on every run, including `main`, because `docs/releases/v0.25.2.mdx` links to three targets that now return HTTP 404: - amd#5858 - amd#5859 - https://github.com/DineshKadiam The PRs and the account no longer exist on GitHub, so the links cannot be restored. This removes the two PR links from the Docs bullet and keeps the contributor credit as plain text without links or PR numbers. Seen on the run for amd#6978 (fix/rag-content-key-unreadable) and the earlier `main` run for amd#6990. The remaining `slack.com/signin` 403 is only a warning and does not fail the job. Verified locally with `python util/check_doc_links.py --internal-only`; no other references to the removed URLs remain under `docs/`.
|
Thanks @tharunsridhar, and for taking the symlink and protected-file hardening too. The description now covers every commit, so nothing else is needed from you. I merged main into the branch; the RAG, |
/index <file> now refuses credentials.json and other protected files the same way /index <folder> skips them. The flagship changelog describes the /index and /resume changes from this PR.
|
Thanks @tharunsridhar, careful work, especially the folder-index symlink and protected-file skip. I merged main, added the flagship changelog entry for the |
Summary
When RAG cannot read a file to hash it,
_content_keyfalls back to a path-only<hash>_notfoundkey.index_documentcarried on with that key, so a PDF, DOCX, XLSX or PPTX (which their libraries open themselves) was extracted and cached under a key that never changes with the file's content. This makesindex_documentstop at that point instead.Because
index_documentnow refuses a file the SDK is not allowed to read, the callers that index a file the user names have to grant that file first. This PR also does that forquick_rag, the chat's/indexcommand and/resume, and corrects the RAG SDK guide.Why
RAGSDK._content_keyreturnssha256(path)_notfoundon anyOSErrorwhile hashing. The docstring says indexing "then fails at extraction", which holds for text formats but not for PDF, DOCX, XLSX and PPTX:_extract_text_from_pdf,_extract_text_from_pptx,_extract_text_from_xlsxand_extract_text_from_docxopen the path directly, so extraction can succeed on a file hashing could not read. The chunks were then cached under a key that is the same before and after every edit, and every later session served the first version's text.Linked issue
Closes #4800
Changes
The fix
src/gaia/rag/sdk.py:index_documentreturns an error (error_type: "file_unreadable", naming the file) when the content key is the_notfoundfallback, before any cache is read or written. New helper_document_unhashable, shaped like_document_locked. The_content_keydocstring now says what happens._content_keyitself is unchanged.is_current()andfreshness()read the_notfoundkey to report "unreadable" or "missing", and several existing tests assert it, so raising there (the issue's suggestion) would have changed all of them. Refusing at the one place that caches is the smaller change.Callers that index a file the user names
Without
allowed_pathsthe SDK only reads the working directory, and PDF, DOCX, XLSX and PPTX used to get past that because their parsers opened the path themselves. Each caller below now grants the file it was given, asAgentSDK._index_named_documentandChatAgent._index_documentsalready do.quick_rag()(src/gaia/rag/sdk.py): grants the file it is given when the caller did not passallowed_paths. An explicitallowed_pathsis left alone./index <path>(hub/agents/gaia/python/gaia_agent/interactive.py): grants the named file before indexing it. For a folder it grants each file the folder holds, one at a time (not the folder), and skips symbolic links and protected files such ascredentials.json. Granting resolves symlinks and does not run the protected-file check, so without the skip a link inside the folder could add its target to the session's file scope./resume <session>(hub/agents/gaia/python/gaia_agent/chat_agent.py,interactive.py): a file is added toindexed_filesonly ifindex_documentsucceeded, and the log says why a document was skipped. The chat now reports "N of M documents" loaded instead of counting what the session file recorded./resumedoes not grant paths: a saved session file must not widen the read scope.Docs
docs/sdk/sdks/rag.mdx: the security note no longer says that withoutallowed_pathsRAG can index any file the process can read. The default scope is the working directory plus granted paths, for every format.Tests
tests/unit/rag/test_cache_key.py: three tests. Hashing fails while extraction would succeed, so indexing is refused, nothing is cached under a_notfoundname and the extractor is never called. The same across two sessions with an edit in between, so the first version's text can't come back. A readable document still indexes.tests/unit/rag/test_quick_rag_scope.py(new):quick_ragon a file outside the working directory, and an explicitallowed_pathsis left alone.hub/agents/gaia/python/tests/test_index_grants_named_file.py(new):/indexof a file outside the working folder, the grant covers that file and not its folder,/indexof a folder, symlinks and protected files in a folder are skipped,/resumelists only documents that indexed, logs why one did not, does not grant a path from the session file, and reports "N of M".tests/test_rag.pyandtests/unit/rag/test_pdf_extraction_errors.py: these build an SDK with only a tempcache_dirand put their PDFs in a temp dir outside the default allowed paths (the current directory). They indexed only because of this bug, so their configs now setallowed_pathsto the temp dir.One behaviour change worth knowing: a PDF, DOCX, XLSX or PPTX outside the SDK's allowed paths is now refused like a text file already was, instead of being read by its parser. The callers above grant the file the user names, so they are unaffected.
Test plan
mainand pass with the change (checked by reverting the source changes).pytest tests/unit/rag tests/test_rag.py: 814 passed, 4 skipped.pytest tests/unit/chat tests/unit/agents/test_read_file_documents.py tests/unit/agents/test_tool_scope.py(the other suites that use RAG): no failures.pytest hub/agents/gaia/python/tests/test_index_grants_named_file.py(withPYTHONPATH=hub/agents/gaia/python): 10 passed.blackandisortclean on the touched files.python util/lint.py --all: not run as a whole. Ranblack,isort(8.0.1) andflake8on the touched files, all clean.Evidence
N/A: an internal change to RAG document indexing. It touches no Agent UI surface, MCP tool, CLI command or HTTP route. I reproduced the bug with a real PDF (hashing forced to fail, file edited, new session): upstream
mainstill held the old text, and this branch refuses withfile_unreadableand holds nothing.Checklist
Closes #4800).docs/sdk/sdks/rag.mdxsecurity note corrected (no other user-facing docs change).