Skip to content

chore(security): replace SheetJS xlsx with exceljs#2945

Merged
steebchen merged 1 commit into
mainfrom
chore/security
Jul 9, 2026
Merged

chore(security): replace SheetJS xlsx with exceljs#2945
steebchen merged 1 commit into
mainfrom
chore/security

Conversation

@steebchen

@steebchen steebchen commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

Resolves the only outstanding high/critical Dependabot security alerts in the repository, both against the SheetJS xlsx package:

Advisory Severity Issue
GHSA-4r6h-8v6p-xvw6 High (CVSS 7.8) Prototype Pollution in SheetJS
GHSA-5pgg-2g8v-p4x9 High (CVSS 7.5) SheetJS Regular Expression Denial of Service (ReDoS)

Why a version bump can't fix this

Both advisories are recorded with introduced: 0 and no fixed version in the npm ecosystem. SheetJS stopped publishing to the npm registry after 0.18.5; patched builds live only on the SheetJS CDN. The repo already pinned the CDN-hosted, patched xlsx@0.20.3, but Dependabot/GHSA still flag it because there is no registry release that clears the advisory range. The only durable remediation is to stop depending on xlsx.

Change

  • apps/api/src/lib/file-extract.ts — migrate knowledge-base spreadsheet extraction from xlsx to the actively-maintained exceljs (no open high/critical advisories). Each worksheet is converted to CSV with standard field quoting (commas, quotes, and newlines are escaped).
  • apps/api/package.json / pnpm-lock.yaml — drop xlsx, add exceljs@4.4.0.
  • apps/playground/.../projects-page-client.tsx — stop advertising legacy binary .xls in the upload picker.
  • file-extract.spec.ts — build fixtures with exceljs; add a CSV-quoting case and a case asserting unreadable spreadsheet data is rejected.

Behavior note

exceljs reads modern .xlsx but cannot parse the legacy binary .xls (BIFF) format. .xlsx extraction is unchanged. Legacy .xls uploads (rare; Excel has defaulted to .xlsx since 2007) are no longer advertised in the picker and are rejected cleanly by the existing upload error path rather than being indexed as garbage.

Verification

  • osv-scanner on the updated lockfile: 0 high/critical remaining (previously 2).
  • pnpm format — clean
  • turbo run build (full monorepo) — passes
  • apps/api unit tests (file-extract.spec.ts) — 6/6 pass; tsc --noEmit clean

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Improved spreadsheet upload handling for modern .xlsx files, including workbooks with multiple sheets (combined output with sheet names).
  • Bug Fixes
    • CSV generation now properly escapes fields (commas, quotes, and newlines).
    • Invalid/unreadable spreadsheet inputs are rejected instead of returning incorrect results.
  • Chores
    • Updated the supported upload formats by removing legacy .xls from the upload flow.

Copilot AI review requested due to automatic review settings July 8, 2026 07:36
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f2f6eb58-fa8e-4901-929d-1dff7c0a29de

📥 Commits

Reviewing files that changed from the base of the PR and between 2415f22 and 60cfc5d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/api/package.json
  • apps/api/src/lib/file-extract.spec.ts
  • apps/api/src/lib/file-extract.ts
  • apps/playground/src/components/playground/projects-page-client.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/api/src/lib/file-extract.spec.ts
  • apps/playground/src/components/playground/projects-page-client.tsx
  • apps/api/src/lib/file-extract.ts
  • apps/api/package.json

Walkthrough

The API spreadsheet extraction path now uses exceljs instead of xlsx. CSV conversion helpers were added for worksheet output, tests were updated for the new workbook format and edge cases, and the playground upload UI no longer accepts .xls files.

Changes

ExcelJS Migration

Layer / File(s) Summary
Dependency swap: xlsx to exceljs
apps/api/package.json
Adds exceljs 4.4.0 and removes the xlsx dependency.
Spreadsheet-to-CSV conversion implementation
apps/api/src/lib/file-extract.ts
Replaces XLSX.read/sheet_to_csv with ExcelJS workbook loading and new csvEscape/sheetToCsv helpers, producing # <sheet name> CSV sections in extractFileText.
Test suite update for exceljs
apps/api/src/lib/file-extract.spec.ts
Adds an exceljs-based buildXlsx helper, updates fixture creation, adds tests for comma-quoted CSV fields and unreadable buffer rejection, and removes the legacy .xls conversion test.
Remove .xls from playground upload UI
apps/playground/src/components/playground/projects-page-client.tsx
Removes .xls from ACCEPTED_FILE_EXTENSIONS and BINARY_FILE_EXTENSIONS.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant extractFileText
  participant ExcelJS
  participant sheetToCsv

  Caller->>extractFileText: uploaded spreadsheet buffer
  extractFileText->>ExcelJS: workbook.xlsx.load(buffer)
  ExcelJS-->>extractFileText: worksheets
  extractFileText->>sheetToCsv: convert each worksheet
  sheetToCsv-->>extractFileText: CSV text
  extractFileText-->>Caller: "# sheet name\nCSV"
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: replacing SheetJS xlsx with exceljs for security reasons.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/security

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/api/src/lib/file-extract.spec.ts (1)

42-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tests are solid — consider adding a quote-escaping case.

The comma-quoting and unreadable-data rejection tests cover the key new behaviors well. One minor gap: csvEscape doubles embedded double quotes (value.replace(/"/g, '""')), but no test exercises that code path. A field like He said "hello" should produce "He said ""hello""" in the CSV output.

🧪 Suggested test for quote escaping
  it("quotes CSV fields containing commas", async () => {
    const buffer = await buildXlsx("Places", [
      ["city", "country"],
      ["Lund, Skåne", "Sweden"],
    ]);

    const text = await extractFileText(
      "places.xlsx",
      "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      buffer,
    );
    expect(text).toContain('"Lund, Skåne",Sweden');
  });

+ it("escapes embedded double quotes in CSV fields", async () => {
+   const buffer = await buildXlsx("Quotes", [
+     ["phrase"],
+     ['He said "hello"'],
+   ]);
+
+   const text = await extractFileText(
+     "quotes.xlsx",
+     "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+     buffer,
+   );
+   expect(text).toContain('"He said ""hello"""');
+ });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/lib/file-extract.spec.ts` around lines 42 - 63, Add a test in
file-extract.spec that covers csvEscape handling of embedded double quotes,
since the current suite only checks comma quoting and unreadable spreadsheet
rejection. Use extractFileText with buildXlsx in the existing file-extract.spec
flow, and assert that a value like He said "hello" is serialized with doubled
quotes in the CSV output. This should exercise the csvEscape path directly and
fit alongside the existing quotes CSV fields containing commas test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/api/src/lib/file-extract.spec.ts`:
- Around line 42-63: Add a test in file-extract.spec that covers csvEscape
handling of embedded double quotes, since the current suite only checks comma
quoting and unreadable spreadsheet rejection. Use extractFileText with buildXlsx
in the existing file-extract.spec flow, and assert that a value like He said
"hello" is serialized with doubled quotes in the CSV output. This should
exercise the csvEscape path directly and fit alongside the existing quotes CSV
fields containing commas test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 48233680-a46c-4e40-9bbf-c5ab6d48615a

📥 Commits

Reviewing files that changed from the base of the PR and between ef91273 and ec2792d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/api/package.json
  • apps/api/src/lib/file-extract.spec.ts
  • apps/api/src/lib/file-extract.ts
  • apps/playground/src/components/playground/projects-page-client.tsx

@steebchen steebchen force-pushed the chore/security branch 2 times, most recently from cffcdb1 to 2415f22 Compare July 9, 2026 13:35
SheetJS xlsx (GHSA-4r6h-8v6p-xvw6 prototype pollution, GHSA-5pgg-2g8v-p4x9
ReDoS) has no fixed version published to the npm registry, so Dependabot
flags every version. Migrate knowledge-base spreadsheet extraction to the
maintained exceljs package and drop the xlsx dependency.

- apps/api/src/lib/file-extract.ts: read .xlsx via exceljs, convert each
  worksheet to CSV with standard quoting.
- Drop advertised legacy binary .xls upload (exceljs cannot parse BIFF);
  such files are rejected cleanly by the upload route.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015JboNg5ryVipY8M6nzJykR
@steebchen steebchen added this pull request to the merge queue Jul 9, 2026
Merged via the queue into main with commit 38447ef Jul 9, 2026
28 checks passed
@steebchen steebchen deleted the chore/security branch July 9, 2026 19:03
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.

3 participants