Repository navigation
fix: guard against patching binary files - #9
Conversation
semantic-release derives the published version from git tags at CI time, so the committed version field was diverging (0.1.3 vs published 0.3.0). Set it to 0.0.0 to signal it's a placeholder managed by release tooling. Co-Authored-By: Claude Code <noreply@anthropic.com>
prepareOperation read files as UTF-8, decoding binary into replacement characters so a patch could match garbage or silently corrupt the file on write. Read as Buffer, reject on a null byte with an EBINARY code before any write, then decode. Delete hunks never read content and are unaffected. Co-Authored-By: Claude Code <noreply@anthropic.com>
This reverts commit 8c0cbc3.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Reviewer's GuideAdds an early null-byte guard for apply_patch updates and moves so binary files cannot be decoded or corrupted, while preserving deletion semantics and reporting structured EBINARY failures without misleading previews. Sequence diagram for binary-safe apply_patch updatessequenceDiagram
participant ApplyPatch
participant Preview as createPatchPreview
participant Prepare as prepareOperation
participant Guard as readTextFileRejectingBinary
participant FS as FileSystem
ApplyPatch->>Preview: createPatchPreview(cwd, hunks)
Preview->>Guard: readTextFileRejectingBinary(absolutePath, filePath)
Guard->>FS: readFile(absolutePath)
alt null byte found
Guard-->>Preview: Error with code EBINARY
Preview-->>ApplyPatch: Structured failure with file path
else text file
Guard-->>Preview: UTF-8 content
Preview->>Prepare: prepareOperation(cwd, hunk)
Prepare->>Guard: readTextFileRejectingBinary(absolutePath, filePath)
Guard-->>Prepare: UTF-8 content
Prepare-->>ApplyPatch: Safe patch operation
end
Flow diagram for binary-safe apply_patch operationsflowchart TD
A[apply_patch update or move] --> B{Operation type}
B -->|delete| C[Preserve existing deletion behavior]
B -->|update or move| D[readTextFileRejectingBinary]
D --> E[readFile]
E --> F{Contains null byte?}
F -->|yes| G[Reject with EBINARY]
G --> H[No decode, write, move, or text preview]
F -->|no| I[Decode as UTF-8]
I --> J[Create preview and prepare write or move]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/index.ts" line_range="1201-1205" />
<code_context>
+ // bytes into replacement characters, so a patch could either match
+ // garbage or silently corrupt the file on write. Reject early with a
+ // distinct code instead of letting apply_patch guess at binary content.
+ const raw = await readFile(absolutePath);
+ if (raw.includes(0)) {
+ throw Object.assign(new Error(`Refusing to patch binary file: ${hunk.filePath}`), { code: "EBINARY" });
+ }
+ const currentContent = raw.toString("utf-8");
const result =
hunk.chunks.length === 0
</code_context>
<issue_to_address>
**issue (broader_impact):** The tool's initial preview path still reads existing update targets with the UTF-8 string overload before `prepareOperation` runs, so a binary file is decoded into replacement characters and can be shown as a misleading text diff before the later `EBINARY` rejection. This violates the change's stated guarantee that binary content is rejected before decoding.
**Triggers:** When the patch is submitted through the `apply_patch` tool and the target contains invalid UTF-8 or binary bytes.
**Suggested fix:** Make preview generation use the same raw-byte null-byte check, or skip text preview for binary targets and let the operation preparation report `EBINARY`.
</issue_to_address>Sourcery assessment
Approval pending. 1 finding to address first.
Blocking findings: src/index.ts:1205
Reuse the null-byte guard when generating update previews so apply_patch falls back to a progress-only pending update rather than decoding binary content. Keep deletes unguarded and cover binary update, move, delete, and preview behavior. Co-Authored-By: Claude Code <noreply@anthropic.com>
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/index.ts" line_range="919-922" />
<code_context>
continue;
}
- const oldContent = await readFile(absolutePath, "utf-8");
+ const oldContent = await readTextFileRejectingBinary(absolutePath, hunk.filePath);
const newContent =
hunk.chunks.length === 0 ? oldContent : replaceChunks(oldContent, hunk.filePath, hunk.chunks).content;
</code_context>
<issue_to_address>
**issue (broader_impact):** Binary delete operations still read the file with `readFile(..., "utf-8")` in the delete branch, so the pending and final tool results include replacement-character text previews for binary contents even though binary update previews are suppressed. This produces the misleading preview the change is intended to prevent.
**Triggers:** When `apply_patch` deletes a binary file through the tool interface.
**Suggested fix:** Read binary deletes as bytes and omit the preview, or detect the null byte in the delete preview path and fall back to the progress-only result.
</issue_to_address>Sourcery assessment
Approval pending. 1 finding to address first.
Blocking findings: src/index.ts:922
| const oldContent = await readFile(absolutePath, "utf-8"); | ||
| const oldContent = await readTextFileRejectingBinary(absolutePath, hunk.filePath); | ||
| const newContent = | ||
| hunk.chunks.length === 0 ? oldContent : replaceChunks(oldContent, hunk.filePath, hunk.chunks).content; | ||
| if (hunk.movePath) { |
There was a problem hiding this comment.
issue (broader_impact): Binary delete operations still read the file with readFile(..., "utf-8") in the delete branch, so the pending and final tool results include replacement-character text previews for binary contents even though binary update previews are suppressed. This produces the misleading preview the change is intended to prevent.
Triggers: When apply_patch deletes a binary file through the tool interface.
Suggested fix: Read binary deletes as bytes and omit the preview, or detect the null byte in the delete preview path and fall back to the progress-only result.
Summary
Reject
apply_patchupdates to files containing a null byte before decoding or writing them. The failure is reported withEBINARYand includes the patched file path, preventing UTF-8 replacement-character corruption.Regression coverage verifies structured
EBINARYfailures, byte-for-byte preservation, binary move rejection, binary deletion, and that pending/final tool results omit misleading text previews.Before opening this PR
Verification
npm run check(typecheck + biome)npm test(127 tests)npm pack --dry-run(release sanity)git diff --check main...HEADTested revision:
7143f90Node / Pi versions: Node
v26.8.1; Pi0.85.1Local OS: macOS (Darwin 25.6.0)
Live Pi results
Report or evidence: Local deterministic regression test covers null-byte rejection and exact byte preservation.
Transport evidence if applicable (grammar / JSON / unverified): Unverified.
Failures, missing coverage, or N/A reason: This is a filesystem/path safety change. Per CONTRIBUTION.md, required developer-run live CRUD and CI on Ubuntu, macOS, and Windows remain outstanding. This PR stays draft until the live tests and CI complete, or a maintainer records a waiver.
apply_patch impact
EBINARY; no breaking migration.🤖 Generated with Claude Code
Summary by Sourcery
Guard apply_patch against binary file updates while preserving safe deletion behavior and accurate tool results.
Bug Fixes:
Enhancements:
Tests: