Skip to content

Fix race condition in getFileIDs that crashes Node.js process on multi-file uploads#33

Open
jnavarrete-ep wants to merge 1 commit intoSendSafely:masterfrom
jnavarrete-ep:fix/getfileids-race-condition
Open

Fix race condition in getFileIDs that crashes Node.js process on multi-file uploads#33
jnavarrete-ep wants to merge 1 commit intoSendSafely:masterfrom
jnavarrete-ep:fix/getfileids-race-condition

Conversation

@jnavarrete-ep
Copy link
Copy Markdown

Summary

SendSafely#encryptAndUploadFiles with ≥2 file-path arguments can crash the
Node.js process with an unhandled TypeError: Cannot read properties of undefined (reading 'name') at lib/SendSafely.js:2902. This PR fixes three
compounding bugs in getFileIDs that together produce the crash.

Stack trace observed in production (SDK 3.0.3, Node 22.21.1)

/app/node_modules/@sendsafely/sendsafely/lib/SendSafely.js:2902
      var serverFilename = (files[index].name === undefined) ? myself.defaultFileName : files[index].name;
                                         ^
TypeError: Cannot read properties of undefined (reading 'name')
    at /app/node_modules/@sendsafely/sendsafely/lib/SendSafely.js:2972:27
    at handleResponse (/app/node_modules/@sendsafely/sendsafely/lib/SendSafely.js:2902:42)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)

Root cause

  1. Index/array mismatch. The non-legacy branch pushes to files in
    fs.stat completion order, then calls handleResponse(i, ..., files, ...)
    where i is the input index. When completion order ≠ input order,
    files[i] is undefined.
  2. Broken sequential chain. Inside _loop, the line p = _p writes to an
    undeclared identifier. In sloppy mode this silently creates a global; the
    outer for loop's p never updates, so all iterations run in parallel
    against the initial Promise.resolve(). This maximises the likelihood of
    bug Remove duplicate event listener for message event #1 manifesting.
  3. Unhandled throw. The TypeError escapes via a .then() callback chain
    that isn't caught upstream, so the consumer's sendsafely.error and
    file.upload.error handlers never see it and the process exits.

Changes

  • Non-legacy branch now uses files[i] = file instead of files.push(file),
    guaranteeing files[i] is populated before handleResponse reads it.
  • handleResponse gains a defensive guard: if files[index] is ever undefined,
    raise server.error instead of throwing.
  • _loop now returns its promise and the for loop reassigns p to the
    return value. Iterations chain sequentially as originally intended.

Risk

Low. No public API changes. The legacy {size, data} upload path is untouched.
No timing-sensitive behaviour changes beyond making iterations actually
sequential (which was already the author's stated intent).

Tests

The repository has no existing test suite. Verified manually by calling
encryptAndUploadFiles with multiple file paths before and after the patch.
Before: intermittent process crash. After: all files upload successfully.

encryptAndUploadFiles with >=2 file paths can crash the Node.js process
with an unhandled TypeError "Cannot read properties of undefined (reading
'name')" at lib/SendSafely.js:2902. Three compounding bugs in getFileIDs
together produce the crash:

1. Index/array mismatch. The non-legacy branch pushed to files in
   fs.stat completion order, then called handleResponse(i, ..., files)
   where i is the input index. When completion order != input order,
   files[i] was undefined. Fixed by assigning files[i] = file.

2. Broken sequential chain. Inside _loop, the line p = _p wrote to an
   undeclared identifier. In sloppy mode this silently created a global;
   the outer for loop's p never updated, so every iteration ran in
   parallel against the initial Promise.resolve(). Fixed by having _loop
   return its chained promise and having the for loop reassign p.

3. Unhandled throw. The TypeError escaped via a .then() callback chain
   that is not caught upstream, so consumer sendsafely.error and
   file.upload.error handlers never saw it. Added a defensive guard in
   handleResponse that raises the existing server.error event instead
   of throwing when files[index] is undefined.

No public API, event names, or parameter shapes change. The legacy
{size, data} upload path is untouched. Repo has no test suite; verified
manually by calling encryptAndUploadFiles with multiple file paths.

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant