fix(segurança): substituir xlsx por exceljs (CVEs sem patch no npm)

xlsx@0.18.5 tem Prototype Pollution e ReDoS sem versão corrigida no
npm. Migrado para exceljs@4.4.0 nos 4 pontos de uso: parser de
importação, geração de template, exportação de lançamentos e
exportação de relatório de categorias.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-04 03:12:04 +00:00
parent 10afef9fec
commit df996df93d
6 changed files with 710 additions and 145 deletions

View File

@@ -45,10 +45,10 @@ export function UploadZone({ onParsed }: UploadZoneProps) {
reader.readAsText(file, "windows-1252");
} else {
const reader = new FileReader();
reader.onload = (e) => {
reader.onload = async (e) => {
try {
const buffer = e.target?.result as ArrayBuffer;
const statement = parseXls(buffer);
const statement = await parseXls(buffer);
onParsed(statement);
} catch (err) {
setError(
@@ -62,8 +62,8 @@ export function UploadZone({ onParsed }: UploadZoneProps) {
}
};
const handleDownloadTemplate = () => {
const bytes = generateXlsTemplate();
const handleDownloadTemplate = async () => {
const bytes = await generateXlsTemplate();
const blob = new Blob([bytes], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});

View File

@@ -32,7 +32,7 @@ interface LancamentosExportProps {
exportContext?: TransactionsExportContext;
}
const loadXlsx = () => import("xlsx");
const loadExcelJS = () => import("exceljs");
const loadPdfDeps = async () => {
const [{ default: jsPDF }, { default: autoTable }] = await Promise.all([
@@ -158,7 +158,7 @@ export function TransactionsExport({
try {
setIsExporting(true);
const transactions = await loadTransactions();
const XLSX = await loadXlsx();
const ExcelJS = await loadExcelJS();
const headers = [
"Data",
@@ -188,23 +188,28 @@ export function TransactionsExport({
rows.push(row);
});
const ws = XLSX.utils.aoa_to_sheet([headers, ...rows]);
const workbook = new ExcelJS.Workbook();
const ws = workbook.addWorksheet("Lançamentos");
ws["!cols"] = [
{ wch: 12 }, // Data
{ wch: 42 }, // Nome
{ wch: 15 }, // Tipo
{ wch: 15 }, // Condição
{ wch: 20 }, // Pagamento
{ wch: 15 }, // Valor
{ wch: 20 }, // Category
{ wch: 20 }, // Conta/Cartão
{ wch: 20 }, // Payer
];
ws.addRows([headers, ...rows]);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Lançamentos");
XLSX.writeFile(wb, getFileName("xlsx"));
const colWidths = [12, 42, 15, 15, 20, 15, 20, 20, 20];
colWidths.forEach((w, i) => {
ws.getColumn(i + 1).width = w;
});
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = getFileName("xlsx");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
toast.success("Lançamentos exportados em Excel com sucesso!");
} catch (error) {