Repository navigation
fix(file_tools): refuse plain-text writes that corrupt binary documents (port of ironclaw#7109) - #82818
fix(file_tools): refuse plain-text writes that corrupt binary documents (port of ironclaw#7109)#82818teknium1 wants to merge 1 commit into
Conversation
Port from nearai/ironclaw#7109: read_file auto-extracts .docx/.xlsx/.pptx (and PDF via anydoc) to readable text, so a model plausibly believes it holds the file's contents and writes the edited text back with write_file/patch — silently destroying the document container. Proven live on main: write_file over a valid .docx left a non-zip corpse, and a text write over an existing .pdf clobbered the %PDF header. - tools/binary_extensions.py: OPAQUE_DOCUMENT_EXTENSIONS + has_opaque_document_extension() + is_pdf_path() (pure string checks) - tools/file_tools.py: _check_binary_document_write() — opaque container formats (doc/docx/xls/xlsx/ppt/pptx/odt/ods/odp) always rejected; .pdf rejected only when overwriting an existing regular file (new-PDF creation stays allowed, matching the upstream split guard). Wired into write_file_tool and patch_tool (replace + V4A Update/Add headers; Delete/Move skip the guard since they write no text). - tests/tools/test_binary_document_write_guard.py: guard unit tests + end-to-end write_file/patch coverage incl. bytes-untouched assertions.
૮ >ﻌ< ა ci reviewran on 9b8e631 — fix(file_tools): refuse plain-text writes that corrupt binar all good! |
|
suggesting changes
Security evidence:
Not checked:
Signed: GPT-5.6-sol-xhigh in Codex |
|
Merged via #86362 (rebase-merge) — your commits cherry-picked with authorship preserved. Follow-up commit added 10 missing extensions ( Thanks for the clean port from ironclaw! |
|
Addressed in #86362 — added |
OPAQUE_DOCUMENT_EXTENSIONS was missing 10 extensions that read_file auto-extracts via anydoc: .docm, .xlsm, .xlsb, .pptm, .ppsx, .ppsm, .pps, .pot, .rtf, .epub. Each has the same corruption path: read_file shows extracted text, model writes it back, container is destroyed. Flagged by @egilewski on PR #82818 — proven live for .docm (text write left a non-zip corpse). Added bytes-untouched regression test for .docm.
OPAQUE_DOCUMENT_EXTENSIONS was missing 10 extensions that read_file auto-extracts via anydoc: .docm, .xlsm, .xlsb, .pptm, .ppsx, .ppsm, .pps, .pot, .rtf, .epub. Each has the same corruption path: read_file shows extracted text, model writes it back, container is destroyed. Flagged by @egilewski on PR NousResearch#82818 — proven live for .docm (text write left a non-zip corpse). Added bytes-untouched regression test for .docm.
OPAQUE_DOCUMENT_EXTENSIONS was missing 10 extensions that read_file auto-extracts via anydoc: .docm, .xlsm, .xlsb, .pptm, .ppsx, .ppsm, .pps, .pot, .rtf, .epub. Each has the same corruption path: read_file shows extracted text, model writes it back, container is destroyed. Flagged by @egilewski on PR NousResearch#82818 — proven live for .docm (text write left a non-zip corpse). Added bytes-untouched regression test for .docm.
OPAQUE_DOCUMENT_EXTENSIONS was missing 10 extensions that read_file auto-extracts via anydoc: .docm, .xlsm, .xlsb, .pptm, .ppsx, .ppsm, .pps, .pot, .rtf, .epub. Each has the same corruption path: read_file shows extracted text, model writes it back, container is destroyed. Flagged by @egilewski on PR NousResearch#82818 — proven live for .docm (text write left a non-zip corpse). Added bytes-untouched regression test for .docm.
Summary
write_file/patchnow refuse plain-text writes that would corrupt binary documents — always for opaque container formats (.doc/.docx/.xls/.xlsx/.ppt/.pptx/.odt/.ods/.odp), and for.pdfonly when overwriting an existing file (new-PDF creation stays allowed).Port of nearai/ironclaw#7109.
Root cause:
read_fileauto-extracts these formats to readable text, so a model plausibly believes it holds the file's contents and writes the edited text back — silently destroying the document container. Proven live on current main before fixing:write_file_toolover a valid.docxleft a non-zip corpse (zipfile.is_zipfile→False), and a text write over an existing.pdfclobbered the%PDFheader.Changes
tools/binary_extensions.py:OPAQUE_DOCUMENT_EXTENSIONS+has_opaque_document_extension()+is_pdf_path()(pure string checks, no I/O)tools/file_tools.py:_check_binary_document_write()gate, wired intowrite_file_toolandpatch_tool(replace mode + V4AUpdate/Addheaders;Delete/Moveskip the guard since they write no text). Error messages point the model to the docx/xlsx/powerpoint/pdf skills and python-docx/openpyxl/python-pptx via terminal — the correct editing path stays fully open.tests/tools/test_binary_document_write_guard.py: guard unit tests + end-to-end write_file/patch coverage with bytes-untouched assertionsAdaptation notes
IronClaw's fix lives in their Rust
codingextension with a read-representation fingerprint (ReadRepresentation::ExtractedText). Hermes doesn't track read representation per scope, so the port uses the simpler always-reject rule for opaque containers (matching theiris_opaque_binary_document_pathhalf exactly) and the existing-file check for PDF (matching their split-guard follow-up commit that unblocked new-PDF authoring). Terminal-based document editing (python-docx etc.) is unaffected — this guards only the text file tools, which are the exact surface that shows extracted text.Validation
write_fileover existing valid.docxwrite_fileto newreport.docxwrite_fileover existing.pdf%PDFheader clobberedwrite_fileto NEW.pdfpatch(replace + V4A Update) on.docx.docxTargeted tests:
tests/tools/test_binary_document_write_guard.py+test_file_write_safety.py+test_file_tools.py— 114 passed. E2E probe with real imports + isolatedHERMES_HOMEconfirmed all four scenarios above against the worktree.Infographic