Skip to content

Conversation

Copy link

Copilot AI commented Jan 8, 2026

User description

The xlsx package (v0.18.5) is unmaintained and has a known Prototype Pollution vulnerability (CVE) with no patch available. While the app only exports (not imports) Excel files, removing the vulnerable dependency is necessary.

Changes

Dependencies

Code (src/app/pages/mapping/mapping.component.ts)

  • Changed import: XLSXExcelJS
  • Converted exportToExcel() to async
  • Replaced XLSX.utils.table_to_sheet() with DOM-based table extraction
  • Added column auto-sizing with configurable constants
  • Maintained same export behavior and filename

Before:

exportToExcel() {
  let element = document.getElementById('excel-table');
  const ws: XLSX.WorkSheet = XLSX.utils.table_to_sheet(element, { raw: true });
  const wb: XLSX.WorkBook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
  XLSX.writeFile(wb, 'DSOMM - Activities.xlsx');
}

After:

async exportToExcel() {
  const element = document.getElementById('excel-table');
  if (!element) {
    console.error('Excel table element not found');
    return;
  }

  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');

  const table = element as HTMLTableElement;
  const rows = Array.from(table.querySelectorAll('tr'));

  rows.forEach(row => {
    const cells = Array.from(row.querySelectorAll('th, td'));
    const rowData = cells.map(cell => cell.textContent?.trim() || '');
    worksheet.addRow(rowData);
  });

  // Auto-fit columns
  worksheet.columns.forEach(column => {
    let maxLength = 0;
    column.eachCell({ includeEmpty: true }, cell => {
      const cellLength = cell.value ? cell.value.toString().length : DEFAULT_COLUMN_WIDTH;
      if (cellLength > maxLength) maxLength = cellLength;
    });
    column.width = Math.min(maxLength + COLUMN_PADDING, MAX_COLUMN_WIDTH);
  });

  const buffer = await workbook.xlsx.writeBuffer();
  const blob = new Blob([buffer], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  });
  const url = window.URL.createObjectURL(blob);
  const anchor = document.createElement('a');
  anchor.href = url;
  anchor.download = 'DSOMM - Activities.xlsx';
  anchor.click();
  window.URL.revokeObjectURL(url);
}

Security Impact

Resolves Dependabot alert #3. CodeQL scan shows 0 new alerts.

Original prompt

Security Issue: Prototype Pollution in xlsx Package

Problem

Dependabot alert #3 identifies a Prototype Pollution vulnerability in the xlsx (SheetJS) package. All versions through 0.19.2 are vulnerable, and the npm package is no longer maintained.

Current version: [email protected]
Status: No patched version available on npm

Current Usage Analysis

The xlsx package is currently used in only one location:

  • File: src/app/pages/mapping/mapping.component.ts
  • Function: exportToExcel() (lines 160-167)
  • Purpose: Export HTML table data to Excel file (write/export only)

Note: Since the application only exports data and doesn't import/read Excel files, it is NOT currently vulnerable to the Prototype Pollution attack. However, migration is necessary because:

  1. The package is unmaintained
  2. Future security updates won't be available
  3. Best practice is to use actively maintained libraries

Solution: Migrate to ExcelJS

Replace xlsx with exceljs, an actively maintained alternative with similar functionality.

Changes Required

1. Update package.json

  • Remove: "xlsx": "^0.18.5"
  • Add: "exceljs": "^4.4.0"

2. Update src/app/pages/mapping/mapping.component.ts

Current code (lines 1-6, 160-167):

import * as XLSX from 'xlsx';

exportToExcel() {
  let element = document.getElementById('excel-table');
  const ws: XLSX.WorkSheet = XLSX.utils.table_to_sheet(element, { raw: true });
  const wb: XLSX.WorkBook = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
  XLSX.writeFile(wb, 'DSOMM - Activities.xlsx');
  console.log(`${perfNow()}: Mapping: Exported to Excel`);
}

Replace with ExcelJS equivalent:

import * as ExcelJS from 'exceljs';

async exportToExcel() {
  const element = document.getElementById('excel-table');
  if (!element) {
    console.error('Excel table element not found');
    return;
  }

  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');

  // Extract table data
  const table = element as HTMLTableElement;
  const rows = Array.from(table.querySelectorAll('tr'));

  rows.forEach((row) => {
    const cells = Array.from(row.querySelectorAll('th, td'));
    const rowData = cells.map(cell => cell.textContent?.trim() || '');
    worksheet.addRow(rowData);
  });

  // Auto-fit columns (optional, improves readability)
  worksheet.columns.forEach(column => {
    let maxLength = 0;
    column.eachCell?.({ includeEmpty: true }, (cell) => {
      const cellLength = cell.value ? cell.value.toString().length : 10;
      if (cellLength > maxLength) {
        maxLength = cellLength;
      }
    });
    column.width = Math.min(maxLength + 2, 50); // Max width of 50
  });

  // Write file
  const buffer = await workbook.xlsx.writeBuffer();
  const blob = new Blob([buffer], { 
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 
  });
  const url = window.URL.createObjectURL(blob);
  const anchor = document.createElement('a');
  anchor.href = url;
  anchor.download = 'DSOMM - Activities.xlsx';
  anchor.click();
  window.URL.revokeObjectURL(url);

  console.log(`${perfNow()}: Mapping: Exported to Excel`);
}

3. Update template (if needed)
If the button/trigger for export uses (click)="exportToExcel()", it should remain unchanged. The function signature changes from sync to async, but Angular handles this automatically.

Acceptance Criteria

  • xlsx package removed from package.json
  • exceljs package added to package.json (version 4.4.0 or higher)
  • package-lock.json updated
  • Import statement changed from import * as XLSX from 'xlsx' to import * as ExcelJS from 'exceljs'
  • exportToExcel() method updated to use ExcelJS API
  • Exported Excel file maintains the same structure and filename
  • Application builds successfully (npm run build)
  • Export functionality works correctly (manual test)
  • No TypeScript compilation errors
  • Dependabot alert Bump ws and socket.io-adapter #3 resolved

Testing

After applying changes:

# Install dependencies
npm install

# Build the application
npm run build

# Run linting
npm run lint

# Verify xlsx is removed
npm list xlsx  # Should show "empty"

# Verify exceljs is installed
npm list exceljs  # Should show version 4.4.0 or higher

# Check for vulnerabilities
npm audit

Manual testing:

  1. Navigate to the Mapping page
  2. Click the "Export to Excel" button
  3. Verify the file DSOMM - Activities.xlsx downloads
  4. Open the file and verify all data is present and correctly formatted

Additional Notes

  • ExcelJS provides richer Excel formatting options if needed in the future
  • The migration is straightforward since only export functionality is used
  • ExcelJS is actively maintained with 2M+ weekly downloads on npm
  • The function becomes async, but this doesn't affect the Angular component's usage

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.


PR Type

Bug fix, Enhancement


Description

  • Migrate from unmaintained xlsx to actively maintained exceljs library

  • Resolve Prototype Pollution vulnerability (CVE) in xlsx package

  • Refactor exportToExcel() to async with DOM-based table extraction

  • Add column auto-sizing with configurable width constraints


Diagram Walkthrough

flowchart LR
  A["[email protected]<br/>Unmaintained<br/>Prototype Pollution CVE"] -->|Replace| B["[email protected]<br/>Actively Maintained<br/>No Vulnerabilities"]
  C["XLSX.utils API<br/>table_to_sheet"] -->|Refactor| D["DOM-based Extraction<br/>querySelectorAll"]
  E["Static Column Width"] -->|Enhance| F["Auto-fit Columns<br/>with Constraints"]
Loading

File Walkthrough

Relevant files
Dependencies
package.json
Replace xlsx with exceljs dependency                                         

package.json

+1/-1     
Enhancement
mapping.component.ts
Refactor Excel export to use ExcelJS library                         

src/app/pages/mapping/mapping.component.ts

  • Changed import from XLSX to ExcelJS
  • Converted exportToExcel() method to async
  • Replaced XLSX.utils API with DOM-based table extraction using
    querySelectorAll
  • Added three configurable constants for column width management
    (DEFAULT_COLUMN_WIDTH, COLUMN_PADDING, MAX_COLUMN_WIDTH)
  • Implemented auto-fit column width calculation with min/max constraints
  • Refactored file download using Blob and URL.createObjectURL instead of
    XLSX.writeFile
  • Added null check for table element with error logging
+48/-7   

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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

Copilot AI changed the title [WIP] Migrate from xlsx package to ExcelJS for data export Migrate from unmaintained xlsx to exceljs to resolve Prototype Pollution vulnerability Jan 8, 2026
Copilot AI requested a review from granatonatalia January 8, 2026 15:14
@granatonatalia granatonatalia marked this pull request as ready for review January 8, 2026 15:17
Copilot AI review requested due to automatic review settings January 8, 2026 15:17
@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Formula injection

Description: The export copies raw table textContent into Excel cells (worksheet.addRow(rowData)),
which can enable Excel formula injection if any cell content is attacker-controlled and
begins with =, +, -, or @ (e.g., =HYPERLINK("http://evil","click")), so consider
sanitizing/escaping such leading characters before writing.
mapping.component.ts [177-181]

Referred Code
rows.forEach(row => {
  const cells = Array.from(row.querySelectorAll('th, td'));
  const rowData = cells.map(cell => cell.textContent?.trim() || '');
  worksheet.addRow(rowData);
});
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing try/catch: The async export path lacks error handling around workbook.xlsx.writeBuffer() and Blob/URL
creation, so failures will surface as unhandled promise rejections and the URL may not be
revoked on error.

Referred Code
async exportToExcel() {
  const element = document.getElementById('excel-table');
  if (!element) {
    console.error('Excel table element not found');
    return;
  }

  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');

  // Extract table data
  const table = element as HTMLTableElement;
  const rows = Array.from(table.querySelectorAll('tr'));

  rows.forEach(row => {
    const cells = Array.from(row.querySelectorAll('th, td'));
    const rowData = cells.map(cell => cell.textContent?.trim() || '');
    worksheet.addRow(rowData);
  });

  // Auto-fit columns (optional, improves readability)


 ... (clipped 23 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Excel formula injection: Table cell text is written directly into Excel cells without sanitizing leading formula
characters (e.g., =, +, -, @), enabling potential Excel formula injection if any table
content is user-controlled.

Referred Code
rows.forEach(row => {
  const cells = Array.from(row.querySelectorAll('th, td'));
  const rowData = cells.map(cell => cell.textContent?.trim() || '');
  worksheet.addRow(rowData);
});

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit context: The new Excel export flow logs an action but does not include user identity, outcome
status, or a reliable audit record as required for critical actions, which may be needed
depending on whether exports are considered auditable events.

Referred Code
async exportToExcel() {
  const element = document.getElementById('excel-table');
  if (!element) {
    console.error('Excel table element not found');
    return;
  }

  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');

  // Extract table data
  const table = element as HTMLTableElement;
  const rows = Array.from(table.querySelectorAll('tr'));

  rows.forEach(row => {
    const cells = Array.from(row.querySelectorAll('th, td'));
    const rowData = cells.map(cell => cell.textContent?.trim() || '');
    worksheet.addRow(rowData);
  });

  // Auto-fit columns (optional, improves readability)


 ... (clipped 25 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured console logs: The added/retained console.* logging is unstructured and may not meet centralized logging
requirements even though it does not appear to include sensitive data.

Referred Code
  console.error('Excel table element not found');
  return;
}

const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Sheet1');

// Extract table data
const table = element as HTMLTableElement;
const rows = Array.from(table.querySelectorAll('tr'));

rows.forEach(row => {
  const cells = Array.from(row.querySelectorAll('th, td'));
  const rowData = cells.map(cell => cell.textContent?.trim() || '');
  worksheet.addRow(rowData);
});

// Auto-fit columns (optional, improves readability)
worksheet.columns.forEach(column => {
  let maxLength = 0;
  column.eachCell({ includeEmpty: true }, cell => {


 ... (clipped 22 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@granatonatalia granatonatalia merged commit 57862fd into master Jan 8, 2026
7 of 9 checks passed
@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Use structured data for Excel export

Refactor the Excel export to use the component's structured dataSource property
instead of scraping data from the HTML table. This decouples the export logic
from the UI, making it more robust.

Examples:

src/app/pages/mapping/mapping.component.ts [173-181]
    // Extract table data
    const table = element as HTMLTableElement;
    const rows = Array.from(table.querySelectorAll('tr'));

    rows.forEach(row => {
      const cells = Array.from(row.querySelectorAll('th, td'));
      const rowData = cells.map(cell => cell.textContent?.trim() || '');
      worksheet.addRow(rowData);
    });

Solution Walkthrough:

Before:

async exportToExcel() {
  const element = document.getElementById('excel-table');
  if (!element) {
    return;
  }

  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');

  // Extract data by scraping the HTML table from the DOM
  const table = element as HTMLTableElement;
  const rows = Array.from(table.querySelectorAll('tr'));

  rows.forEach(row => {
    const cells = Array.from(row.querySelectorAll('th, td'));
    const rowData = cells.map(cell => cell.textContent?.trim() || '');
    worksheet.addRow(rowData);
  });

  // ... write file
}

After:

async exportToExcel() {
  const workbook = new ExcelJS.Workbook();
  const worksheet = workbook.addWorksheet('Sheet1');

  // Use structured data directly from the component's dataSource
  const headers = this.displayedColumns; // e.g., ['dimension', 'domain', ...]
  worksheet.addRow(headers);

  this.dataSource.data.forEach(mappingRow => {
    const rowValues = headers.map(header => mappingRow[header]);
    worksheet.addRow(rowValues);
  });

  // ... auto-fit columns and write file
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a major design flaw where the export logic relies on fragile DOM scraping, and proposes a much more robust solution using the component's structured dataSource.

High
Possible issue
Prevent potential file download failures

Prevent potential download failures by delaying the revokeObjectURL call with
setTimeout and ensuring the anchor element is added to and removed from the DOM.

src/app/pages/mapping/mapping.component.ts [200-205]

 const url = window.URL.createObjectURL(blob);
 const anchor = document.createElement('a');
 anchor.href = url;
 anchor.download = 'DSOMM - Activities.xlsx';
+document.body.appendChild(anchor); // Append to body to ensure it's clickable
 anchor.click();
-window.URL.revokeObjectURL(url);
+document.body.removeChild(anchor); // Clean up the DOM
+setTimeout(() => window.URL.revokeObjectURL(url), 0);
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential race condition where the object URL might be revoked before the download starts, and provides a robust solution using setTimeout and DOM manipulation.

Medium
Fix incorrect column width calculation

Fix the column width calculation by setting the length of empty cells to 0
instead of a default width, preventing columns from becoming unnecessarily wide.

src/app/pages/mapping/mapping.component.ts [183-193]

 // Auto-fit columns (optional, improves readability)
 worksheet.columns.forEach(column => {
   let maxLength = 0;
   column.eachCell({ includeEmpty: true }, cell => {
-    const cellLength = cell.value ? cell.value.toString().length : DEFAULT_COLUMN_WIDTH;
+    const cellLength = cell.value ? cell.value.toString().length : 0;
     if (cellLength > maxLength) {
       maxLength = cellLength;
     }
   });
-  column.width = Math.min(maxLength + COLUMN_PADDING, MAX_COLUMN_WIDTH);
+  const finalWidth = maxLength > 0 ? maxLength : DEFAULT_COLUMN_WIDTH;
+  column.width = Math.min(finalWidth + COLUMN_PADDING, MAX_COLUMN_WIDTH);
 });
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a logic flaw in the new column width calculation that could lead to unnecessarily wide columns, and the proposed fix is accurate.

Low
General
Add error handling for buffer write

Add a try/catch block around the workbook.xlsx.writeBuffer() call to handle
potential errors during Excel file generation.

src/app/pages/mapping/mapping.component.ts [196]

-const buffer = await workbook.xlsx.writeBuffer();
+let buffer;
+try {
+  buffer = await workbook.xlsx.writeBuffer();
+} catch (error) {
+  console.error('Failed to write Excel buffer', error);
+  return;
+}
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly proposes adding error handling for an async operation (writeBuffer), which improves the robustness of the function by preventing unhandled promise rejections.

Low
  • More

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR resolves a Prototype Pollution security vulnerability by migrating from the unmaintained xlsx package (v0.18.5) to the actively maintained exceljs library (v4.4.0). The migration updates the Excel export functionality to use ExcelJS's API while maintaining the same export behavior.

Key changes:

  • Replaced vulnerable xlsx dependency with exceljs@^4.4.0
  • Converted exportToExcel() method to async with enhanced error handling and column auto-sizing
  • Maintained identical export behavior and filename for end users

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
package.json Removed xlsx@^0.18.5 and added exceljs@^4.4.0
src/app/pages/mapping/mapping.component.ts Updated import statement, converted exportToExcel() to use ExcelJS API with DOM-based table extraction, added column auto-sizing constants and logic, converted method to async

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

worksheet.addRow(rowData);
});

// Auto-fit columns (optional, improves readability)
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The comment states "Auto-fit columns (optional, improves readability)" but this code is not optional - it's being executed unconditionally. The comment should be updated to reflect that this is the actual behavior, not an optional feature. Consider changing to "Auto-fit columns to improve readability" or removing the "(optional, improves readability)" portion.

Suggested change
// Auto-fit columns (optional, improves readability)
// Auto-fit columns to improve readability

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants