Skip to content

T1334951 - devextreme-exceljs-fork - Path traversal in Workbook.addImage({ filename }) - #155

Merged
bit-byte0 merged 19 commits into
masterfrom
fix/addimage-path-traversal
Sep 14, 2026
Merged

bit-byte0 merged 19 commits into
masterfrom
fix/addimage-path-traversal

Conversation

@bit-byte0

@bit-byte0 bit-byte0 commented Sep 8, 2026 •

Copy link
Copy Markdown

What

addImage / addMedia now reject unsafe media paths (a .. traversal segment, a null byte, or an extension containing path separators) across both the regular and streaming writers, closing the arbitrary file read reported in CVE-2026-78208 (CWE-73). A new utils.safeJoin(baseDir, userPath) helper is also exported so applications can safely combine a trusted base directory with user input

How

a shared guard in utils.js runs at both addImage entry points and the addMedia read sites, and safeJoin resolves the path then verifies it stays inside the given base directory

Copilot AI lite review requested due to automatic review settings September 8, 2026 07:15
@bit-byte0 bit-byte0 added bug Something isn't working javascript Pull requests that update javascript code labels Sep 8, 2026
@bit-byte0 bit-byte0 self-assigned this Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Size Change: +1.92 kB (+0.13%)

Total Size: 1.45 MB

📦 View Changed
Filename Size Change
dist/dx-exceljs-fork.bare.js 432 kB +667 B (+0.15%)
dist/dx-exceljs-fork.bare.min.js 264 kB +289 B (+0.11%)
dist/dx-exceljs-fork.js 473 kB +578 B (+0.12%)
dist/dx-exceljs-fork.min.js 285 kB +382 B (+0.13%)

compressed-size-action

@bit-byte0 bit-byte0 changed the title fix: reject path-traversal filenames in addImage/addMedia (T1334951) T1334951 - devextreme-exceljs-fork - Path traversal in Workbook.addImage({ filename }) Sep 8, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The traversal validation is bypassable via non-string fs.readFile inputs and is not yet applied/covered for the streaming WorkbookWriter code path.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR mitigates a path traversal risk where Workbook.addImage({ filename }) could pass attacker-controlled paths to fs.readFile, enabling unintended file reads and embedding into generated XLSX files.

Changes:

  • Introduces utils.assertSafeMediaPath(filename) and enforces it in Workbook#addImage and the XLSX media write path.
  • Adds unit and integration coverage for rejecting filenames containing .. path segments.
  • Updates integration tests to use path.resolve for stable image fixture paths and documents the new throwing behavior in the TypeScript declarations.
File summaries
File Description
spec/unit/utils/utils.spec.js Adds unit coverage for assertSafeMediaPath behavior.
spec/integration/workbook/images.spec.js Adds integration regression test for Workbook#addImage traversal rejection.
spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js Updates fixture path handling for streaming writer integration tests.
lib/xlsx/xlsx.js Enforces safe-path assertion before reading media from disk during XLSX write.
lib/utils/utils.js Introduces assertSafeMediaPath helper for traversal-segment rejection.
lib/doc/workbook.js Calls safe-path assertion in Workbook#addImage.
index.d.ts Documents that addImage can throw on traversal segments.
Review details
  • Files reviewed: 6/7 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/doc/workbook.js
Comment thread lib/utils/utils.js Outdated
Comment thread spec/integration/workbook-xlsx-writer/workbook-xlsx-writer.spec.js
Comment thread spec/unit/utils/utils.spec.js Outdated
Copilot AI review requested due to automatic review settings September 8, 2026 11:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The added traversal guard leaves other user-controlled path components (notably image.extension) unvalidated even though they are interpolated into XLSX internal paths, which can still permit path-segment injection in generated archives.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 7/8 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread lib/doc/workbook.js
Comment thread lib/stream/xlsx/workbook-writer.js
Comment thread lib/utils/utils.js Outdated
Copilot AI review requested due to automatic review settings September 8, 2026 12:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The security checks are consistently applied at the relevant entry points and write-time read sites, with accompanying unit and integration tests.

Review details

Suppressed comments (1)

lib/utils/utils.js:83

  • assertSafeMediaPath throws an error that says "Unsafe image filename", but this helper is named for (and used as) a general media guard. Align the error message with the function/API naming to reduce confusion when it’s thrown from non-image media contexts.
      throw new Error(`Unsafe image filename: expected a string but received ${typeof filename}`);
    }
    if (filename.split(/[\\/]/).includes('..')) {
      throw new Error(`Unsafe image filename "${filename}": path traversal ("..") segments are not allowed`);
  • Files reviewed: 7/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread lib/utils/utils.js Outdated
Copilot AI review requested due to automatic review settings September 8, 2026 13:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new safeJoin assertions hard-code POSIX expectations that will fail on Windows CI, and the TypeScript export for utils.safeJoin doesn’t align with the browser/bare entrypoints.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 8/9 changed files
  • Comments generated: 4
  • Review effort level: Lite

Comment thread lib/utils/utils.js Outdated
Comment thread lib/utils/utils.js Outdated
Comment thread spec/integration/workbook/images.spec.js Outdated
Comment thread spec/unit/utils/utils.spec.js Outdated
Copilot AI review requested due to automatic review settings September 8, 2026 13:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new ExcelJS.utils.safeJoin typing/export appears to be Node-only while the browser/bare bundles don’t export utils, creating a likely runtime/type mismatch for browser consumers.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

lib/utils/utils.js:74

  • getRelsPath now uses a local variable named path, which shadows the newly imported Node path module in this file and makes the code harder to read/maintain (especially with the new safeJoin implementation that relies on path). Rename the local variable to avoid the shadowing.
  • Files reviewed: 8/9 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread lib/dx-exceljs-fork.nodejs.js
Comment thread lib/utils/utils.js Outdated
Copilot AI review requested due to automatic review settings September 8, 2026 14:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new typings and Node-only ExcelJS.utils.safeJoin export currently mismatch the browser entrypoint/runtime surface and the updated addImage throw behavior is under-documented in index.d.ts.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 8/9 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread lib/utils/utils.js Outdated
Comment thread README.md Outdated
});
```

`addImage` validates `filename` and `extension` values to prevent path traversal attacks. These parameters cannot contain the following strings: `..`, `\`, and `/`. You can also use `ExcelJS.utils.safeJoin()` instead of `path.join()` to validate file paths upon generation.

@arman-boyakhchyan arman-boyakhchyan Sep 10, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`addImage` validates `filename` and `extension` values to prevent path traversal attacks. These parameters cannot contain the following strings: `..`, `\`, and `/`. You can also use `ExcelJS.utils.safeJoin()` instead of `path.join()` to validate file paths upon generation.
`addImage` validates `filename` and `extension` values to prevent path traversal attacks. These parameters cannot contain the following strings:
`filename`: `\0` and `..`
`extension`: `\0`, `..`, `\`, and `/`
You can also use `ExcelJS.utils.safeJoin()` instead of `path.join()` to validate file paths upon generation.

Comment thread lib/utils/media-path-guard.js Outdated
const hasTraversalSegment = /(^|[\\/])\.\.([\\/]|$)/.test(filename);
const hasWindowsDriveRelative = /^[a-zA-Z]:\.\./.test(filename);
if (hasTraversalSegment || hasWindowsDriveRelative) {
throw new Error(`File path cannot contain "..", "/", or "\\": "${filename}"`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new Error(`File path cannot contain "..", "/", or "\\": "${filename}"`);
throw new Error(`File path cannot contain "..": "${filename}"`);

Comment thread index.d.ts Outdated
/**
* Add Image to Workbook and return the id
* Adds Image to Workbook and returns an id.
* Fails if `img.filename` contains ".."

@arman-boyakhchyan arman-boyakhchyan Sep 10, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Fails if `img.filename` contains ".."
* Fails if `img.filename` contains ".." or "\\0"
* or if `img.extension` contains "..", "\\", "/", or "\\0"

Copilot AI review requested due to automatic review settings September 10, 2026 12:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new documentation/typings currently misstate or omit key details of the implemented validation rules (notably how .. is detected and the Node-only availability of safeJoin), which could mislead consumers of this security fix.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 12/13 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread README.md Outdated
Comment thread index.d.ts Outdated

/**
* Add Image to Workbook and return the id
* Adds Image to Workbook and returns an id.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Adds Image to Workbook and returns an id.
* Adds an image to the workbook and returns an ID.

Comment thread index.d.ts Outdated

export namespace utils {
/**
* Checks that `userPath` does not resolve to a path outside of `baseDir`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Checks that `userPath` does not resolve to a path outside of `baseDir`.
* Checks to ensure that `userPath` does not resolve to a path outside of `baseDir`.

Copilot AI review requested due to automatic review settings September 11, 2026 06:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

safeJoin can be bypassed through symlinked paths, and the documentation overstates validation behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

README.md:2020

  • The documented rule is broader than the implementation: assertSafeMediaPath rejects .. only when it forms a traversal segment, and the added unit test explicitly accepts a..b.png. Please describe the segment rule rather than saying any .. string is forbidden.
`filename`: `\0` and `..`
  • Files reviewed: 12/13 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread lib/utils/safe-join.js
Comment thread README.md
`extension`: `\0`, `..`, `\`, and `/`

You can also use `ExcelJS.utils.safeJoin()` instead of `path.join()` to validate file paths upon generation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> [!Important]
> `safeJoin()` cannot detect symbolic links. If you pass a symbolic link to this function that escapes the base directory, `safeJoin()` does not throw an error.

Comment thread index.d.ts
/**
* Checks to ensure that `userPath` does not resolve to a path outside of `baseDir`.
* Use instead of `path.join` when you calculate a path for `addImage({ filename })`
* from user input. Fails if the resolved path escapes `baseDir`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* from user input. Fails if the resolved path escapes `baseDir`.
* from user input. Fails if the resolved path escapes `baseDir`.
* Cannot detect symbolic links that escape the base directory.

Comment thread README.md Outdated
Comment on lines +2020 to +2021
`filename`: `\0` and `..`
`extension`: `\0`, `..`, `\`, and `/`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`filename`: `\0` and `..`
`extension`: `\0`, `..`, `\`, and `/`
- `filename`: `\0` and `..`
- `extension`: `\0`, `..`, `\`, and `/`

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Unresolved moderate findings remain for write-time regression coverage and documentation alignment.

Review details

Suppressed comments (5)

README.md:2020

  • This list implies that any .. substring is forbidden in filename, while the guard deliberately accepts names such as a..b.png and only rejects traversal segments. Clarify the README so users are not given a stricter contract than the implementation enforces.
`filename`: `\0` and `..`

README.md:2034

  • The new standalone Node example leaves both userInput and workbook undefined, so copying it fails before demonstrating safeJoin. Define the input and instantiate a workbook (or explicitly state that these come from surrounding application code).
const filename = ExcelJS.utils.safeJoin('/app/assets', userInput);
const imageId = workbook.addImage({ filename, extension: 'png' });

lib/stream/xlsx/workbook-writer.js:235

  • The streaming writer's write-time guard is not covered by the new tests. Because getImage() returns the stored media object, callers can mutate filename after registration; add a regression test that mutates it before commit() and asserts the commit rejects the unsafe path, so this second enforcement point is protected from regressions.
          mediaPathGuard.assertSafeImage(medium);

lib/xlsx/xlsx.js:429

  • The write-time guard is not covered by the new integration tests. getImage() exposes the stored media object, so a caller can change filename after addImage() has returned; add a regression test that mutates that object before writing and verifies the write rejects the unsafe path, otherwise this security check could regress while the current entry-point tests still pass.
          mediaPathGuard.assertSafeImage(medium);

package.json:3

  • Please keep the package version unchanged in a feature/fix PR. The repository guidance in README.md:64-65 says versions are updated at release time to avoid merge collisions, so this security change should not bump the package from 4.4.13 here.
  "version": "4.5.0",
  • Files reviewed: 12/13 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 11, 2026 07:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Absolute paths remain permitted, leaving the arbitrary-file-read issue unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (4)

README.md:2020

  • The guard accepts filenames such as a..b.png because it only rejects .. path-traversal segments (as the new unit test on media-path-guard.spec.js:11 confirms), so this documentation overstates the restriction. Describe the filename rule in terms of traversal segments rather than saying it cannot contain the .. string anywhere.
- `filename`: `\0` and `..`

README.md:2034

  • workbook is not declared in this newly added standalone Node.js example, so copying it produces a ReferenceError after safeJoin succeeds. Instantiate the workbook before calling addImage or show the required existing setup.
const imageId = workbook.addImage({ filename, extension: 'png' });

lib/stream/xlsx/workbook-writer.js:235

  • The streaming test only verifies rejection during addImage, not at this write site. Mutating wb.getImage(id).filename after insertion is a supported way to reach this second defense; add a commit-time regression test so the zip.file read path remains protected.
          mediaPathGuard.assertSafeImage(medium);

lib/xlsx/xlsx.js:429

  • The added entry-point tests do not exercise this second defense. A caller can mutate wb.getImage(id).filename after addImage, and this is the check that must prevent the subsequent fs.readFile; add a regression test that performs that mutation before writing so this protection cannot be removed without detection.
          mediaPathGuard.assertSafeImage(medium);
  • Files reviewed: 12/13 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread lib/utils/media-path-guard.js
@bit-byte0
bit-byte0 merged commit ec265bd into master Sep 14, 2026
17 checks passed
@bit-byte0
bit-byte0 deleted the fix/addimage-path-traversal branch September 14, 2026 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants