fix(security): 2 improvements across 2 files#228
fix(security): 2 improvements across 2 files#228tomaioo wants to merge 2 commits intoOpenCoworkAI:mainfrom
Conversation
- Security: TOCTOU race in defensive file reader can bypass type/size checks - Security: Unbounded IPC payload size for export can enable renderer-to-main DoS Signed-off-by: tomaioo <203048277+tomaioo@users.noreply.github.com>
- Security: TOCTOU race in defensive file reader can bypass type/size checks - Security: Unbounded IPC payload size for export can enable renderer-to-main DoS Signed-off-by: tomaioo <203048277+tomaioo@users.noreply.github.com>
There was a problem hiding this comment.
Findings
- [Blocker]
open()can hang on FIFOs before the new regular-file check runs —safeReadImportFile()now callsopen(path, 'r')beforefileHandle.stat(), so a path swapped to a named pipe will block the Electron main process instead of returningnull. I reproduced this on the Linux runner withmkfifoplusfs.promises.open(), which timed out after 2s. Evidence:apps/desktop/src/main/imports/safe-read.ts:32.
Suggested fix:This keeps the single-descriptor flow that fixes the TOCTOU issue, but avoids blocking on FIFO/socket-style paths beforeimport { constants } from 'node:fs'; import { open } from 'node:fs/promises'; const flags = constants.O_RDONLY | (constants.O_NONBLOCK ?? 0); fileHandle = await open(path, flags);
fileHandle.stat()can reject them.
Summary
- Review mode: initial
- 1 blocker found in the new
safeReadImportFile()flow. The TOCTOU fix is directionally right, but the currentopen(path, 'r')ordering reintroduces a main-process DoS path for named pipes atapps/desktop/src/main/imports/safe-read.ts:32.
Testing
- Not run (automation). Suggested regression tests: add a POSIX-only
safe-readcase proving a FIFO path returnsnullwithout blocking; add aparseRequest()case for oversizedhtmlContent.
open-codesign Bot
| let fileHandle: Awaited<ReturnType<typeof open>>; | ||
| try { | ||
| stats = await stat(path); | ||
| fileHandle = await open(path, 'r'); |
There was a problem hiding this comment.
open(path, 'r') runs before the regular-file check, so a contributor can replace this path with a FIFO and block the main process before fileHandle.stat() executes. On this Linux runner, mkfifo + fs.promises.open() hung until timeout killed it.
Suggested fix:
import { constants } from 'node:fs';
import { open } from 'node:fs/promises';
const flags = constants.O_RDONLY | (constants.O_NONBLOCK ?? 0);
fileHandle = await open(path, flags);That preserves the single-handle TOCTOU fix while preventing FIFO/socket-style paths from hanging the process.
There was a problem hiding this comment.
Review mode: initial
Findings
-
[Minor] Missing tests for the security fix — the TOCTOU fix in
safeReadImportFileand the size limit inparseRequestare both security-sensitive paths with multiple error branches. The project uses Vitest and requires tests for changes like these. Neither function has a test file touched in this PR.
Suggested fix: Add Vitest tests covering:safeReadImportFile: ENOENT returns null, non-file handle returns null, oversized file returns null, successful file read returns content, file handle is always closed (including through error paths).parseRequest: oversized htmlContent throwsCodesignError, normal content passes through.
-
[Nit] Unused import in
safe-read.ts— the new code importsopenandstatfromnode:fs/promises, but thestatimport is unused because allstatcalls go throughfileHandle.stat().
Suggested fix:-import { open, stat } from 'node:fs/promises'; +import { open } from 'node:fs/promises';
Summary
The PR correctly addresses a TOCTOU race condition in safeReadImportFile by opening the file once and using the file descriptor for both stat and read. It also adds a missing size limit check for HTML export content in parseRequest. Both changes are well-motivated and implement the described fix correctly. No security regressions, no new dependencies, no hardcoded UI values. The main gap is test coverage for these security-sensitive paths.
Testing
Not run (automation). Suggested tests listed above.
Open-CoDesign Bot
Summary
fix(security): 2 improvements across 2 files
Problem
Severity:
Medium| File:apps/desktop/src/main/imports/safe-read.ts:L34safeReadImportFileperformsstat(path)to validate file type and size, then separately callsreadFile(path). An attacker with local write access to the same filesystem location can race-replace the file between these calls (e.g., swap a small regular file for a FIFO/device/very large file), bypassing the earlier validation and potentially causing hangs or memory pressure in the main process.Solution
Open the file once and validate/read via the same file descriptor to avoid path re-resolution races. For example:
const fh = await open(path, O_RDONLY | O_NOFOLLOW)(where supported), thenconst st = await fh.stat(), enforcest.isFile()and size cap, thenawait fh.readFile('utf8'), finally close handle infinally.Changes
apps/desktop/src/main/imports/safe-read.ts(modified)apps/desktop/src/main/exporter-ipc.ts(modified)