Skip to content

fix(file_tools): refuse plain-text writes that corrupt binary documents - #86362

Merged
kshitijk4poor merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/binary-doc-write-guard
Aug 14, 2026
Merged

kshitijk4poor merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/binary-doc-write-guard

Conversation

@kshitijk4poor

Copy link
Copy Markdown

Summary

write_file / patch now refuse plain-text writes that would corrupt binary documents — always for opaque container formats (.doc/.docx/.docm/.xls/.xlsx/.xlsm/.xlsb/.ppt/.pps/.pot/.pptx/.pptm/.ppsx/.ppsm/.odt/.ods/.odp/.rtf/.epub), and for .pdf only when overwriting an existing file (new-PDF creation stays allowed).

Port of nearai/ironclaw#7109. Based on #82818 by @teknium1.

Root cause: read_file auto-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_tool over a valid .docx left a non-zip corpse, and a text write over an existing .pdf clobbered the %PDF header.

Changes

  • tools/binary_extensions.py: OPAQUE_DOCUMENT_EXTENSIONS (19 extensions covering all anydoc-extracted container formats) + has_opaque_document_extension() + is_pdf_path() (pure string checks, no I/O)
  • tools/file_tools.py: _check_binary_document_write() gate, wired into write_file_tool and patch_tool (replace mode + 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 with bytes-untouched assertions

Salvage notes

Cherry-picked from #82818 by @teknium1 (authorship preserved). Follow-up commit adds 10 missing extensions (.docm, .xlsm, .xlsb, .pptm, .ppsx, .ppsm, .pps, .pot, .rtf, .epub) that read_file auto-extracts via anydoc but were absent from the original OPAQUE_DOCUMENT_EXTENSIONS set. Flagged by @egilewski — proven live for .docm (text write left a non-zip corpse). Added bytes-untouched regression test for .docm.

Validation

Scenario Before After
write_file over existing valid .docx silent corruption (zip destroyed) refused, bytes untouched
write_file over existing .docm silent corruption (zip destroyed) refused, bytes untouched
write_file to new report.docx writes invalid "docx" refused with skill guidance
write_file over existing .pdf %PDF header clobbered refused, bytes untouched
write_file to NEW .pdf allowed still allowed
patch (replace + V4A Update) on .docx corrupts refused
V4A Delete of .docx works still works (guard skipped)

Targeted tests: 17/17 passed. E2E probe with real imports + isolated HERMES_HOME confirmed all scenarios.

Closes #82818

teknium1 and others added 2 commits August 15, 2026 02:45
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.
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.
@coderabbitai

coderabbitai Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 93a26fbd-26c5-4be4-85d2-8033c8261dce

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

Lazy import inside _check_binary_document_write was unnecessary —
binary_extensions is a leaf module already imported at line 15.
Hoisted has_opaque_document_extension and is_pdf_path to the existing
module-level import. /simplify-code finding.
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/tools Tool registry, model_tools, toolsets tool/file File tools (read, write, patch, search) labels Aug 14, 2026
@kshitijk4poor
kshitijk4poor merged commit 9f004c8 into NousResearch:main Aug 14, 2026
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tools Tool registry, model_tools, toolsets P1 High — major feature broken, no workaround tool/file File tools (read, write, patch, search) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants