fix: resolve build failures, security issues, and architectural improvements

Critical Fixes:
- Remove missing sw_combine.js from prebuild script and all documentation
- Remove missing test-safety-check.sh from test:all script
- Add build:web:build alias to fix docker commands
- Fix syntax errors in validate-critical-files.sh script

Security:
- Fix Electron path traversal vulnerability in export-data-to-downloads handler
  - Sanitize file names using basename() to prevent directory traversal
  - Enforce allowed file extensions (.json, .txt, .csv, .md, .log)
  - Add validation for empty names, path separators, and length limits

Architecture Improvements:
- Add queue size guard to CapacitorPlatformService (max 1000 operations)
  - Fail-fast when queue is full to prevent memory exhaustion
  - Add warning at 80% capacity
  - Add getQueueTelemetry() method for monitoring queue health
  - Track peak queue size for diagnostics

- Standardize environment variable usage in PlatformServiceFactory
  - Prefer import.meta.env.VITE_PLATFORM (standard Vite pattern)
  - Maintain backward compatibility with process.env fallback

Documentation:
- Clarify PWA status: remove misleading VitePWA comments
- Update BUILDING.md to reflect removed sw_combine.js step
- Update build-arch-guard.sh to remove sw_combine.js from protected files

All changes maintain backward compatibility and improve code quality.
This commit is contained in:
Matthew Raymer
2026-01-01 10:54:07 +00:00
parent f64846ae17
commit 5247a37fac
9 changed files with 165 additions and 70 deletions

View File

@@ -6,7 +6,7 @@ import electronIsDev from 'electron-is-dev';
import unhandled from 'electron-unhandled';
// import { autoUpdater } from 'electron-updater';
import { promises as fs } from 'fs';
import { join } from 'path';
import { join, basename } from 'path';
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
@@ -151,15 +151,47 @@ app.on('activate', async function () {
* This provides a secure, native way to save files directly to the Downloads
* directory using the main process's file system access.
*
* Security: File names are sanitized to prevent path traversal attacks.
* Only safe file extensions are allowed (.json, .txt, .csv, .md).
*
* @param fileName - The name of the file to save (including extension)
* @param data - The data to write to the file (string or buffer)
* @returns Promise<{success: boolean, path?: string, error?: string}>
*/
ipcMain.handle('export-data-to-downloads', async (_event, fileName: string, data: string) => {
try {
// Security: Sanitize file name to prevent path traversal
// 1. Extract only the basename (removes any directory components)
const sanitizedBaseName = basename(fileName);
// 2. Reject if still contains path separators (shouldn't happen after basename, but double-check)
if (sanitizedBaseName.includes('/') || sanitizedBaseName.includes('\\')) {
throw new Error('Invalid file name: path separators not allowed');
}
// 3. Enforce allowed file extensions for security
const allowedExtensions = ['.json', '.txt', '.csv', '.md', '.log'];
const hasAllowedExtension = allowedExtensions.some(ext =>
sanitizedBaseName.toLowerCase().endsWith(ext.toLowerCase())
);
if (!hasAllowedExtension) {
throw new Error(`Invalid file extension. Allowed: ${allowedExtensions.join(', ')}`);
}
// 4. Additional validation: reject empty or suspicious names
if (!sanitizedBaseName || sanitizedBaseName.trim().length === 0) {
throw new Error('File name cannot be empty');
}
// 5. Reject names that are too long (prevent potential filesystem issues)
if (sanitizedBaseName.length > 255) {
throw new Error('File name too long (max 255 characters)');
}
// Get the user's Downloads directory path
const downloadsDir = app.getPath('downloads');
const filePath = join(downloadsDir, fileName);
const filePath = join(downloadsDir, sanitizedBaseName);
// Write the file to the Downloads directory
await fs.writeFile(filePath, data, 'utf-8');