fix: security hardening, tests, docs for Electron desktop & memory optimization

## Security Fixes
- Sanitize OMNIROUTE_MEMORY_MB with parseInt + range validation (64-16384)
  to prevent command injection via spawn() args
- Validate URL protocol in shell.openExternal (http/https only)
  to prevent RCE in Electron renderer compromise
- Bump default memory from 256MB to 512MB

## Electron package-lock.json
- Added to .gitignore (5278 lines removed from tracking)

## Test Suite (64 tests, 9 suites)
- electron-main.test.mjs: URL validation, IPC channels, window handler
- electron-preload.test.mjs: channel whitelist, API surface, open-external
- cli-memory.test.mjs: injection prevention, boundary values, .env parsing

## Documentation
- Desktop App section added to all 30 READMEs (9 fully translated)
- USER_GUIDE.md updated with 512MB default
- .env.example reflects new defaults
This commit is contained in:
diegosouzapw
2026-02-28 07:59:38 -03:00
parent 177507bbc8
commit d3ace8d611
38 changed files with 1390 additions and 5376 deletions

3
.gitignore vendored
View File

@@ -102,3 +102,6 @@ app.log
# Backup directories
app.__qa_backup/
# Electron (subproject dependency lock)
electron/package-lock.json

View File

@@ -247,6 +247,29 @@ docker compose --profile cli up -d
---
---
## 🖥️ تطبيق سطح المكتب — غير متصل ومتاح دائمًا
> 🆕 **جديد!** أصبح OmniRoute متاحًا الآن كـ **تطبيق سطح مكتب أصلي** لنظام Windows وmacOS وLinux.
- 🖥️ **نافذة أصلية** — نافذة مخصصة مع تكامل شريط النظام
- 🔄 **بدء تلقائي** — تشغيل OmniRoute عند تسجيل الدخول
- 🔔 **إشعارات أصلية** — تنبيهات عند نفاد الحصة
-**تثبيت بنقرة واحدة** — NSIS (Windows)، DMG (macOS)، AppImage (Linux)
- 🌐 **وضع غير متصل** — يعمل بالكامل بدون إنترنت
```bash
npm run electron:dev # وضع التطوير
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 التوثيق الكامل: [`electron/README.md`](electron/README.md)
---
## 💰 لمحة سريعة عن الأسعار
| الطبقة | مقدم | التكلفة | إعادة ضبط الحصص | الأفضل لـ |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Ценообразуването с един поглед
| Ниво | Доставчик | Цена | Нулиране на квота | Най-добро за |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Prissætning på et øjeblik
| Tier | Udbyder | Omkostninger | Kvote nulstilling | Bedst til |

View File

@@ -247,6 +247,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop-App — Offline & Immer Aktiv
> 🆕 **NEU!** OmniRoute ist jetzt als **native Desktop-Anwendung** für Windows, macOS und Linux verfügbar.
- 🖥️ **Natives Fenster** — Dediziertes App-Fenster mit System-Tray-Integration
- 🔄 **Autostart** — OmniRoute beim Systemstart starten
- 🔔 **Native Benachrichtigungen** — Warnungen bei Kontingent-Erschöpfung
-**Ein-Klick-Installation** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline-Modus** — Funktioniert vollständig offline mit integriertem Server
```bash
npm run electron:dev # Entwicklungsmodus
npm run electron:build # Aktuelle Plattform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Vollständige Dokumentation: [`electron/README.md`](electron/README.md)
---
## 💰 Preisübersicht
| Tier | Anbieter | Kosten | Kontingent-Reset | Am besten für |

View File

@@ -247,6 +247,34 @@ docker compose --profile cli up -d
---
---
## 🖥️ Aplicación de Escritorio — Sin Conexión y Siempre Activo
> 🆕 **¡NUEVO!** OmniRoute ahora está disponible como **aplicación de escritorio nativa** para Windows, macOS y Linux.
Ejecuta OmniRoute como una aplicación de escritorio autónoma — sin terminal, sin navegador, sin internet necesario para modelos locales. La app basada en Electron incluye:
- 🖥️ **Ventana Nativa** — Ventana dedicada con integración en la bandeja del sistema
- 🔄 **Inicio Automático** — Inicia OmniRoute al iniciar sesión
- 🔔 **Notificaciones Nativas** — Recibe alertas sobre cuota o problemas de proveedores
-**Instalación con Un Clic** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Modo Sin Conexión** — Funciona completamente offline con servidor incluido
### Inicio Rápido
```bash
npm run electron:dev # Modo desarrollo
npm run electron:build # Plataforma actual
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Documentación completa: [`electron/README.md`](electron/README.md)
---
## 💰 Precios Resumidos
| Tier | Proveedor | Costo | Reset de Cuota | Mejor Para |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Hinnoittelu yhdellä silmäyksellä
| Taso | Palveluntarjoaja | Kustannukset | Kiintiön nollaus | Paras |

View File

@@ -247,6 +247,32 @@ docker compose --profile cli up -d
---
---
## 🖥️ Application Bureau — Hors Ligne et Toujours Actif
> 🆕 **NOUVEAU !** OmniRoute est maintenant disponible en tant qu'**application de bureau native** pour Windows, macOS et Linux.
Exécutez OmniRoute en tant qu'application de bureau autonome — sans terminal, sans navigateur, sans internet requis pour les modèles locaux.
- 🖥️ **Fenêtre Native** — Fenêtre dédiée avec intégration dans la barre d'état système
- 🔄 **Démarrage Automatique** — Lancez OmniRoute à la connexion système
- 🔔 **Notifications Natives** — Alertes pour l'épuisement de quota ou les problèmes
-**Installation en Un Clic** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Mode Hors Ligne** — Fonctionne entièrement hors ligne avec serveur intégré
```bash
npm run electron:dev # Mode développement
npm run electron:build # Plateforme actuelle
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Documentation complète : [`electron/README.md`](electron/README.md)
---
## 💰 Aperçu des tarifs
| Tier | Fournisseur | Coût | Réinitialisation | Idéal pour |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 תמחור במבט חטוף
| שכבה | ספק | עלות | איפוס מכסה | הטוב ביותר עבור |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Árazás egy pillantásra
| Tier | Szolgáltató | Költség | Kvóta visszaállítása | Legjobb a |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Sekilas tentang Harga
| Tingkat | Penyedia | Biaya | Reset Kuota | Terbaik Untuk |

View File

@@ -144,6 +144,30 @@ docker run -d \
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 मूल्य निर्धारण एक नज़र में
| टियर | प्रदाता | लागत | कोटा रीसेट | के लिए सर्वश्रेष्ठ |

View File

@@ -247,6 +247,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Panoramica Prezzi
| Tier | Provider | Costo | Reset Quota | Ideale Per |

View File

@@ -247,6 +247,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ デスクトップアプリ — オフライン&常時稼働
> 🆕 **新機能!** OmniRouteが**ネイティブデスクトップアプリケーション**としてWindows、macOS、Linuxで利用可能になりました。
- 🖥️ **ネイティブウィンドウ** — システムトレイ統合付きの専用ウィンドウ
- 🔄 **自動起動** — システムログイン時にOmniRouteを起動
- 🔔 **ネイティブ通知** — クォータ枯渇やプロバイダー問題のアラート
-**ワンクリックインストール** — NSIS (Windows)、DMG (macOS)、AppImage (Linux)
- 🌐 **オフラインモード** — 内蔵サーバーで完全オフライン動作
```bash
npm run electron:dev # 開発モード
npm run electron:build # 現在のプラットフォーム
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 完全なドキュメント:[`electron/README.md`](electron/README.md)
---
## 💰 価格の概要
| 階層 | プロバイダー | コスト | クォータのリセット | 最適な用途 |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ 데스크톱 앱 — 오프라인 & 상시 가동
> 🆕 **새 기능!** OmniRoute가 Windows, macOS, Linux용 **네이티브 데스크톱 앱**으로 출시되었습니다.
- 🖥️ **네이티브 윈도우** — 시스템 트레이 통합 전용 창
- 🔄 **자동 시작** — 시스템 로그인 시 OmniRoute 실행
- 🔔 **네이티브 알림** — 할당량 소진 또는 공급자 문제 알림
-**원클릭 설치** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **오프라인 모드** — 내장 서버로 완전 오프라인 작동
```bash
npm run electron:dev # 개발 모드
npm run electron:build # 현재 플랫폼
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 전체 문서: [`electron/README.md`](electron/README.md)
---
## 💰 가격 한눈에 보기
| 계층 | 공급자 | 비용 | 할당량 재설정 | 최고의 대상 |

View File

@@ -259,6 +259,43 @@ docker compose --profile cli up -d
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
Run OmniRoute as a standalone desktop app — no terminal, no browser, no internet required for local models. The Electron-based app includes:
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
### Quick Start
```bash
# Development mode
npm run electron:dev
# Build for your platform
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg) — x64 & arm64
npm run electron:build:linux # Linux (.AppImage)
```
### System Tray
When minimized, OmniRoute lives in your system tray with quick actions:
- Open dashboard
- Change server port
- Quit application
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Pricing at a Glance
| Tier | Provider | Cost | Quota Reset | Best For |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Sekilas Pandang Harga
| Peringkat | Pembekal | Kos | Set Semula Kuota | Terbaik Untuk |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Prijzen in één oogopslag
| Niveau | Aanbieder | Kosten | Quotum opnieuw instellen | Beste voor |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Priser på et øyeblikk
| Nivå | Leverandør | Kostnad | Kvote Tilbakestill | Best for |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Pagpepresyo sa isang Sulyap
| Tier | Provider | Gastos | I-reset ang Quota | Pinakamahusay Para sa |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Ceny w skrócie
| Poziom | Dostawca | Koszt | Reset przydziału | Najlepsze dla |

View File

@@ -247,6 +247,45 @@ docker compose --profile cli up -d
---
---
## 🖥️ Aplicativo Desktop — Offline e Sempre Ativo
> 🆕 **NOVO!** O OmniRoute agora está disponível como **aplicativo desktop nativo** para Windows, macOS e Linux.
Execute o OmniRoute como um aplicativo desktop autônomo — sem terminal, sem navegador, sem internet necessária para modelos locais. O app baseado em Electron inclui:
- 🖥️ **Janela Nativa** — Janela dedicada com integração à bandeja do sistema
- 🔄 **Iniciar Automaticamente** — Inicie o OmniRoute ao fazer login no sistema
- 🔔 **Notificações Nativas** — Receba alertas sobre esgotamento de cota ou problemas
-**Instalação com Um Clique** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Modo Offline** — Funciona totalmente offline com servidor embarcado
### Início Rápido
```bash
# Modo desenvolvimento
npm run electron:dev
# Compilar para sua plataforma
npm run electron:build # Plataforma atual
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg) — x64 & arm64
npm run electron:build:linux # Linux (.AppImage)
```
### Bandeja do Sistema
Quando minimizado, o OmniRoute fica na bandeja do sistema com ações rápidas:
- Abrir dashboard
- Alterar porta do servidor
- Sair do aplicativo
📖 Documentação completa: [`electron/README.md`](electron/README.md)
---
## 💰 Preços Resumidos
| Tier | Provedor | Custo | Reset de Cota | Melhor Para |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Visão geral dos preços
| Nível | Provedor | Custo | Redefinição de cota | Melhor para |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Prețurile dintr-o privire
| Nivelul | Furnizor | Cost | Resetare cotă | Cel mai bun pentru |

View File

@@ -247,6 +247,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Настольное Приложение — Оффлайн и Всегда Активно
> 🆕 **НОВИНКА!** OmniRoute теперь доступен как **нативное настольное приложение** для Windows, macOS и Linux.
- 🖥️ **Нативное Окно** — Выделенное окно с интеграцией в системный трей
- 🔄 **Автозапуск** — Запуск OmniRoute при входе в систему
- 🔔 **Нативные Уведомления** — Оповещения об исчерпании квоты
-**Установка в Один Клик** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Оффлайн Режим** — Полностью работает без интернета
```bash
npm run electron:dev # Режим разработки
npm run electron:build # Текущая платформа
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Полная документация: [`electron/README.md`](electron/README.md)
---
## 💰 Обзор цен
| Tier | Провайдер | Стоимость | Сброс квоты | Лучше всего для |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Prehľad cien
| Úroveň | Poskytovateľ | Náklady | Obnovenie kvóty | Najlepšie pre |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Prissättning i en överblick
| Nivå | Leverantör | Kostnad | Kvotåterställning | Bäst för |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 ราคาโดยสรุป
| ชั้น | ผู้ให้บริการ | ราคา | รีเซ็ตโควต้า | ดีที่สุดสำหรับ |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Короткий огляд цін
| Рівень | Постачальник | Вартість | Скидання квоти | Найкраще для |

View File

@@ -249,6 +249,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ Desktop App — Offline & Always-On
> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux.
- 🖥️ **Native Window** — Dedicated app window with system tray integration
- 🔄 **Auto-Start** — Launch OmniRoute on system login
- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues
-**One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux)
- 🌐 **Offline Mode** — Works fully offline with bundled server
```bash
npm run electron:dev # Development mode
npm run electron:build # Current platform
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 Full documentation: [`electron/README.md`](electron/README.md)
---
## 💰 Sơ lược về giá
| Bậc | Nhà cung cấp | Chi phí | Đặt lại hạn ngạch | Tốt nhất cho |

View File

@@ -247,6 +247,30 @@ docker compose --profile cli up -d
---
---
## 🖥️ 桌面应用 — 离线 & 始终在线
> 🆕 **全新!** OmniRoute 现已提供适用于 Windows、macOS 和 Linux 的**原生桌面应用程序**。
- 🖥️ **原生窗口** — 专属应用窗口,集成系统托盘
- 🔄 **自动启动** — 系统登录时启动 OmniRoute
- 🔔 **原生通知** — 配额耗尽或提供商问题时收到提醒
-**一键安装** — NSIS (Windows)、DMG (macOS)、AppImage (Linux)
- 🌐 **离线模式** — 内置服务器,完全离线工作
```bash
npm run electron:dev # 开发模式
npm run electron:build # 当前平台
npm run electron:build:win # Windows (.exe)
npm run electron:build:mac # macOS (.dmg)
npm run electron:build:linux # Linux (.AppImage)
```
📖 完整文档:[`electron/README.md`](electron/README.md)
---
## 💰 定价概览
| 层级 | 提供商 | 费用 | 配额重置 | 最适合 |

View File

@@ -91,7 +91,7 @@ if (args.includes("--help") || args.includes("-h")) {
\x1b[1mConfig:\x1b[0m
Loads .env from: ~/.omniroute/.env or ./.env
Memory limit: OMNIROUTE_MEMORY_MB (default: 256)
Memory limit: OMNIROUTE_MEMORY_MB (default: 512)
\x1b[1mAfter starting:\x1b[0m
Dashboard: http://localhost:<dashboard-port>
@@ -175,7 +175,10 @@ if (!existsSync(serverJs)) {
// ── Start server ───────────────────────────────────────────
console.log(` \x1b[2m⏳ Starting server...\x1b[0m\n`);
const memoryLimit = process.env.OMNIROUTE_MEMORY_MB || "256";
// Sanitize memory limit — parseInt to prevent command injection (#150)
const rawMemory = parseInt(process.env.OMNIROUTE_MEMORY_MB || "512", 10);
const memoryLimit =
Number.isFinite(rawMemory) && rawMemory >= 64 && rawMemory <= 16384 ? rawMemory : 512;
const env = {
...process.env,

View File

@@ -361,7 +361,7 @@ npm run start
For servers with limited RAM, use the memory limit option:
```bash
# With 256MB limit (default)
# With 512MB limit (default)
pm2 start npm --name omniroute -- start
# Or with custom memory limit
@@ -375,19 +375,21 @@ Create `ecosystem.config.js`:
```javascript
module.exports = {
apps: [{
name: 'omniroute',
script: 'npm',
args: 'start',
env: {
NODE_ENV: 'production',
OMNIROUTE_MEMORY_MB: '256',
JWT_SECRET: 'your-secret',
INITIAL_PASSWORD: 'your-password',
apps: [
{
name: "omniroute",
script: "npm",
args: "start",
env: {
NODE_ENV: "production",
OMNIROUTE_MEMORY_MB: "512",
JWT_SECRET: "your-secret",
INITIAL_PASSWORD: "your-password",
},
node_args: "--max-old-space-size=512",
max_memory_restart: "300M",
},
node_args: '--max-old-space-size=256',
max_memory_restart: '300M',
}]
],
};
```
@@ -405,23 +407,23 @@ For host-integrated mode with CLI binaries, see the Docker section in the main d
### Environment Variables
| Variable | Default | Description |
| ----------------------- | ------------------------------------ | ------------------------------------------------------- |
| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT signing secret (**change in production**) |
| `INITIAL_PASSWORD` | `123456` | First login password |
| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) |
| `PORT` | framework default | Service port (`20128` in examples) |
| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) |
| `NODE_ENV` | runtime default | Set `production` for deploy |
| `BASE_URL` | `http://localhost:20128` | Server-side internal base URL |
| `CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL |
| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | HMAC secret for generated API keys |
| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` |
| `ENABLE_REQUEST_LOGS` | `false` | Enables request/response logs |
| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) |
| `OMNIROUTE_MEMORY_MB` | `256` | Node.js heap limit in MB |
| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries |
| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries |
| Variable | Default | Description |
| ------------------------- | ------------------------------------ | ------------------------------------------------------- |
| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT signing secret (**change in production**) |
| `INITIAL_PASSWORD` | `123456` | First login password |
| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) |
| `PORT` | framework default | Service port (`20128` in examples) |
| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) |
| `NODE_ENV` | runtime default | Set `production` for deploy |
| `BASE_URL` | `http://localhost:20128` | Server-side internal base URL |
| `CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL |
| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | HMAC secret for generated API keys |
| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` |
| `ENABLE_REQUEST_LOGS` | `false` | Enables request/response logs |
| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) |
| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB |
| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries |
| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries |
For the full environment variable reference, see the [README](../README.md).

View File

@@ -1,14 +1,14 @@
/**
* OmniRoute Electron Desktop App - Main Process
*
*
* This is the entry point for the Electron desktop application.
* It manages the main window, system tray, and IPC communication.
*/
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, shell } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
const fs = require('fs');
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, shell } = require("electron");
const path = require("path");
const { spawn } = require("child_process");
const fs = require("fs");
// Single instance lock - prevent multiple instances
const gotTheLock = app.requestSingleInstanceLock();
@@ -17,7 +17,7 @@ if (!gotTheLock) {
process.exit(0);
}
app.on('second-instance', () => {
app.on("second-instance", () => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
@@ -26,13 +26,13 @@ app.on('second-instance', () => {
});
// Environment detection
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
const isDev = process.env.NODE_ENV === "development" || !app.isPackaged;
const isProduction = !isDev;
// Paths
const APP_PATH = app.getAppPath();
const RESOURCES_PATH = isProduction ? process.resourcesPath : APP_PATH;
const NEXT_SERVER_PATH = path.join(RESOURCES_PATH, 'app');
const NEXT_SERVER_PATH = path.join(RESOURCES_PATH, "app");
// State
let mainWindow = null;
@@ -52,41 +52,50 @@ function createWindow() {
height: 900,
minWidth: 1024,
minHeight: 700,
title: 'OmniRoute',
icon: path.join(RESOURCES_PATH, 'assets', 'icon.png'),
title: "OmniRoute",
icon: path.join(RESOURCES_PATH, "assets", "icon.png"),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
webSecurity: true,
},
show: false,
backgroundColor: '#0a0a0a',
titleBarStyle: 'hiddenInset',
backgroundColor: "#0a0a0a",
titleBarStyle: "hiddenInset",
trafficLightPosition: { x: 16, y: 16 },
});
// Load the Next.js app
if (isDev) {
mainWindow.loadURL(getServerUrl());
mainWindow.webContents.openDevTools({ mode: 'detach' });
mainWindow.webContents.openDevTools({ mode: "detach" });
} else {
mainWindow.loadURL(getServerUrl());
}
// Show window when ready
mainWindow.once('ready-to-show', () => {
mainWindow.once("ready-to-show", () => {
mainWindow.show();
});
// Handle external links
// Handle external links — validate URL protocol to prevent RCE (#150)
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
try {
const parsedUrl = new URL(url);
if (["http:", "https:"].includes(parsedUrl.protocol)) {
shell.openExternal(url);
} else {
console.warn("[Electron] Blocked external URL with unsafe protocol:", parsedUrl.protocol);
}
} catch {
console.error("[Electron] Invalid external URL blocked:", url);
}
return { action: "deny" };
});
// Handle window close
mainWindow.on('close', (event) => {
mainWindow.on("close", (event) => {
if (!app.isQuitting) {
event.preventDefault();
mainWindow.hide();
@@ -94,7 +103,7 @@ function createWindow() {
return false;
});
mainWindow.on('closed', () => {
mainWindow.on("closed", () => {
mainWindow = null;
});
}
@@ -103,9 +112,9 @@ function createWindow() {
* Create system tray icon
*/
function createTray() {
const iconPath = path.join(RESOURCES_PATH, 'assets', 'tray-icon.png');
const iconPath = path.join(RESOURCES_PATH, "assets", "tray-icon.png");
let icon;
try {
icon = nativeImage.createFromPath(iconPath);
if (icon.isEmpty()) {
@@ -119,7 +128,7 @@ function createTray() {
const contextMenu = Menu.buildFromTemplate([
{
label: 'Open OmniRoute',
label: "Open OmniRoute",
click: () => {
if (mainWindow) {
mainWindow.show();
@@ -128,25 +137,25 @@ function createTray() {
},
},
{
label: 'Open Dashboard',
label: "Open Dashboard",
click: () => {
shell.openExternal(getServerUrl());
},
},
{ type: 'separator' },
{ type: "separator" },
{
label: 'Server Port',
label: "Server Port",
submenu: [
{ label: `Port: ${serverPort}`, enabled: false },
{ type: 'separator' },
{ label: '20128', click: () => changePort(20128) },
{ label: '3000', click: () => changePort(3000) },
{ label: '8080', click: () => changePort(8080) },
{ type: "separator" },
{ label: "20128", click: () => changePort(20128) },
{ label: "3000", click: () => changePort(3000) },
{ label: "8080", click: () => changePort(8080) },
],
},
{ type: 'separator' },
{ type: "separator" },
{
label: 'Quit',
label: "Quit",
click: () => {
app.isQuitting = true;
app.quit();
@@ -154,10 +163,10 @@ function createTray() {
},
]);
tray.setToolTip('OmniRoute');
tray.setToolTip("OmniRoute");
tray.setContextMenu(contextMenu);
tray.on('double-click', () => {
tray.on("double-click", () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
@@ -182,35 +191,35 @@ function changePort(port) {
*/
function startNextServer() {
if (isDev) {
console.log('Development mode: Connect to existing Next.js server');
console.log("Development mode: Connect to existing Next.js server");
return;
}
const serverScript = path.join(NEXT_SERVER_PATH, 'server.js');
const serverScript = path.join(NEXT_SERVER_PATH, "server.js");
if (!fs.existsSync(serverScript)) {
console.error('Server script not found:', serverScript);
console.error("Server script not found:", serverScript);
return;
}
console.log('Starting Next.js server...');
nextServer = spawn('node', [serverScript], {
console.log("Starting Next.js server...");
nextServer = spawn("node", [serverScript], {
cwd: NEXT_SERVER_PATH,
env: {
...process.env,
PORT: String(serverPort),
NODE_ENV: 'production',
NODE_ENV: "production",
},
stdio: 'inherit',
stdio: "inherit",
});
nextServer.on('error', (err) => {
console.error('Failed to start server:', err);
nextServer.on("error", (err) => {
console.error("Failed to start server:", err);
});
nextServer.on('exit', (code) => {
console.log('Server exited with code:', code);
nextServer.on("exit", (code) => {
console.log("Server exited with code:", code);
});
}
@@ -229,7 +238,7 @@ function stopNextServer() {
*/
function setupIpcHandlers() {
// Get app info
ipcMain.handle('get-app-info', () => ({
ipcMain.handle("get-app-info", () => ({
name: app.getName(),
version: app.getVersion(),
platform: process.platform,
@@ -238,39 +247,39 @@ function setupIpcHandlers() {
}));
// Open external URL
ipcMain.handle('open-external', (event, url) => {
ipcMain.handle("open-external", (event, url) => {
try {
const parsedUrl = new URL(url);
if (['http:', 'https:'].includes(parsedUrl.protocol)) {
if (["http:", "https:"].includes(parsedUrl.protocol)) {
shell.openExternal(url);
}
} catch {
console.error('Invalid URL:', url);
console.error("Invalid URL:", url);
}
});
// Get data directory
ipcMain.handle('get-data-dir', () => {
return app.getPath('userData');
ipcMain.handle("get-data-dir", () => {
return app.getPath("userData");
});
// Restart server
ipcMain.handle('restart-server', async () => {
ipcMain.handle("restart-server", async () => {
const serverToStop = nextServer;
stopNextServer();
if (serverToStop) {
await new Promise(resolve => serverToStop.once('exit', resolve));
await new Promise((resolve) => serverToStop.once("exit", resolve));
}
startNextServer();
return { success: true };
});
// Window controls
ipcMain.on('window-minimize', () => {
ipcMain.on("window-minimize", () => {
mainWindow?.minimize();
});
ipcMain.on('window-maximize', () => {
ipcMain.on("window-maximize", () => {
if (mainWindow) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
@@ -280,7 +289,7 @@ function setupIpcHandlers() {
}
});
ipcMain.on('window-close', () => {
ipcMain.on("window-close", () => {
mainWindow?.close();
});
}
@@ -293,7 +302,7 @@ app.whenReady().then(() => {
setupIpcHandlers();
// macOS: recreate window when dock icon clicked
app.on('activate', () => {
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
} else if (mainWindow) {
@@ -303,23 +312,23 @@ app.whenReady().then(() => {
});
// Quit when all windows are closed (except on macOS)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
// Clean up before quit
app.on('before-quit', () => {
app.on("before-quit", () => {
app.isQuitting = true;
stopNextServer();
});
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.on("uncaughtException", (error) => {
console.error("Uncaught Exception:", error);
});
process.on('unhandledRejection', (reason) => {
console.error('Unhandled Rejection:', reason);
process.on("unhandledRejection", (reason) => {
console.error("Unhandled Rejection:", reason);
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
/**
* Tests for CLI Memory Sanitization (bin/omniroute.mjs)
*
* Tests cover:
* - Memory limit parsing and validation
* - Command injection prevention
* - Boundary values
* - .env file loading
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
// ─── Memory Limit Sanitization Tests ─────────────────────────
describe("CLI Memory Limit Sanitization", () => {
/**
* Replicate the memory sanitization logic from bin/omniroute.mjs
*/
function sanitizeMemoryLimit(envValue) {
const rawMemory = parseInt(envValue || "512", 10);
return Number.isFinite(rawMemory) && rawMemory >= 64 && rawMemory <= 16384 ? rawMemory : 512;
}
it("should default to 512 when no env var set", () => {
assert.equal(sanitizeMemoryLimit(undefined), 512);
assert.equal(sanitizeMemoryLimit(null), 512);
assert.equal(sanitizeMemoryLimit(""), 512);
});
it("should accept valid numeric values", () => {
assert.equal(sanitizeMemoryLimit("128"), 128);
assert.equal(sanitizeMemoryLimit("256"), 256);
assert.equal(sanitizeMemoryLimit("512"), 512);
assert.equal(sanitizeMemoryLimit("1024"), 1024);
assert.equal(sanitizeMemoryLimit("2048"), 2048);
assert.equal(sanitizeMemoryLimit("4096"), 4096);
assert.equal(sanitizeMemoryLimit("8192"), 8192);
});
it("should accept boundary values", () => {
assert.equal(sanitizeMemoryLimit("64"), 64); // minimum
assert.equal(sanitizeMemoryLimit("16384"), 16384); // maximum
});
it("should reject values below minimum (64MB)", () => {
assert.equal(sanitizeMemoryLimit("0"), 512);
assert.equal(sanitizeMemoryLimit("1"), 512);
assert.equal(sanitizeMemoryLimit("32"), 512);
assert.equal(sanitizeMemoryLimit("63"), 512);
assert.equal(sanitizeMemoryLimit("-1"), 512);
assert.equal(sanitizeMemoryLimit("-9999"), 512);
});
it("should reject values above maximum (16384MB)", () => {
assert.equal(sanitizeMemoryLimit("16385"), 512);
assert.equal(sanitizeMemoryLimit("99999"), 512);
assert.equal(sanitizeMemoryLimit("1000000"), 512);
});
// ── Command Injection Prevention ──────────────────────────
it("should prevent command injection via shell metacharacters", () => {
// parseInt('256; rm -rf /') returns 256 which is valid — but the sanitized
// integer value is safe because it's a pure number, not a shell string
const r1 = sanitizeMemoryLimit("256; rm -rf /");
assert.equal(typeof r1, "number"); // always returns a safe number
assert.ok(r1 >= 64 && r1 <= 16384); // within safe range
// These produce NaN via parseInt → fallback to 512
assert.equal(sanitizeMemoryLimit("`id`"), 512);
assert.equal(sanitizeMemoryLimit("$(whoami)"), 512);
});
it("should prevent injection via Node.js spawn args", () => {
// These would be dangerous if passed unsanitized to spawn()
assert.equal(sanitizeMemoryLimit("--require=/tmp/malware.js"), 512);
assert.equal(sanitizeMemoryLimit("--experimental-modules"), 512);
assert.equal(sanitizeMemoryLimit("-e 'process.exit()'"), 512);
});
it("should handle non-numeric strings gracefully", () => {
assert.equal(sanitizeMemoryLimit("abc"), 512);
assert.equal(sanitizeMemoryLimit("twelve"), 512);
assert.equal(sanitizeMemoryLimit("NaN"), 512);
assert.equal(sanitizeMemoryLimit("Infinity"), 512);
assert.equal(sanitizeMemoryLimit("undefined"), 512);
});
it("should handle special numeric formats", () => {
// parseInt('0x100') = 0 (stops at 'x'), outside 6416384? No, 0 < 64 → fallback
assert.equal(sanitizeMemoryLimit("0x100"), 512);
// parseInt('1e3') = 1, which is < 64 → fallback
assert.equal(sanitizeMemoryLimit("1e3"), 512);
assert.equal(sanitizeMemoryLimit("512.7"), 512); // parseInt truncates → 512 ✅
assert.equal(sanitizeMemoryLimit(" 256 "), 256); // whitespace → 256 ✅
});
});
// ─── .env Loading Tests ──────────────────────────────────────
describe("CLI .env File Loading", () => {
/**
* Simulate the .env parsing logic from bin/omniroute.mjs
*/
function parseEnvLine(line) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) return null;
const eqIdx = trimmed.indexOf("=");
if (eqIdx === -1) return null;
const key = trimmed.substring(0, eqIdx).trim();
let value = trimmed.substring(eqIdx + 1).trim();
// Remove surrounding quotes
value = value.replace(/^["']|["']$/g, "");
return { key, value };
}
it("should parse KEY=VALUE lines", () => {
const result = parseEnvLine("PORT=3000");
assert.deepEqual(result, { key: "PORT", value: "3000" });
});
it("should strip surrounding quotes", () => {
const dq = parseEnvLine('JWT_SECRET="my-secret"');
assert.deepEqual(dq, { key: "JWT_SECRET", value: "my-secret" });
const sq = parseEnvLine("JWT_SECRET='my-secret'");
assert.deepEqual(sq, { key: "JWT_SECRET", value: "my-secret" });
});
it("should skip comments", () => {
assert.equal(parseEnvLine("# This is a comment"), null);
assert.equal(parseEnvLine(" # Indented comment"), null);
});
it("should skip empty lines", () => {
assert.equal(parseEnvLine(""), null);
assert.equal(parseEnvLine(" "), null);
});
it("should skip lines without =", () => {
assert.equal(parseEnvLine("MALFORMED_LINE"), null);
});
it("should handle values with equals signs", () => {
const result = parseEnvLine("DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require");
assert.equal(result.key, "DATABASE_URL");
assert.equal(result.value, "postgres://user:pass@host:5432/db?sslmode=require");
});
it("should handle empty values", () => {
const result = parseEnvLine("EMPTY_VAR=");
assert.deepEqual(result, { key: "EMPTY_VAR", value: "" });
});
});

View File

@@ -0,0 +1,201 @@
/**
* Tests for Electron main process (electron/main.js)
*
* Tests cover:
* - URL validation in shell.openExternal
* - IPC handler security (open-external validates protocols)
* - Window open handler security
* - Server lifecycle (start/stop/restart)
* - Tray menu structure
* - Port change logic
*/
import { describe, it, mock } from "node:test";
import assert from "node:assert/strict";
// ─── URL Validation Tests ────────────────────────────────────
describe("Electron URL Validation", () => {
/**
* Simulate the open-external IPC handler logic from main.js
*/
function validateExternalUrl(url) {
try {
const parsedUrl = new URL(url);
if (["http:", "https:"].includes(parsedUrl.protocol)) {
return { allowed: true, url };
}
return { allowed: false, reason: `Blocked protocol: ${parsedUrl.protocol}` };
} catch {
return { allowed: false, reason: "Invalid URL" };
}
}
it("should allow http URLs", () => {
const result = validateExternalUrl("http://example.com");
assert.equal(result.allowed, true);
});
it("should allow https URLs", () => {
const result = validateExternalUrl("https://github.com/diegosouzapw/OmniRoute");
assert.equal(result.allowed, true);
});
it("should block file:// protocol (RCE risk)", () => {
const result = validateExternalUrl("file:///etc/passwd");
assert.equal(result.allowed, false);
assert.match(result.reason, /Blocked protocol/);
});
it("should block javascript: protocol (XSS risk)", () => {
const result = validateExternalUrl("javascript:alert(1)");
assert.equal(result.allowed, false);
});
it("should block custom protocol handlers", () => {
const result = validateExternalUrl("vscode://extensions/install?name=malware");
assert.equal(result.allowed, false);
});
it("should block data: URIs", () => {
const result = validateExternalUrl("data:text/html,<script>alert(1)</script>");
assert.equal(result.allowed, false);
});
it("should reject empty string", () => {
const result = validateExternalUrl("");
assert.equal(result.allowed, false);
});
it("should reject malformed URL", () => {
const result = validateExternalUrl("not a url");
assert.equal(result.allowed, false);
assert.match(result.reason, /Invalid URL/);
});
it("should allow localhost URLs", () => {
const result = validateExternalUrl("http://localhost:20128/dashboard");
assert.equal(result.allowed, true);
});
it("should allow URLs with paths and query params", () => {
const result = validateExternalUrl("https://example.com/path?q=test&page=1#hash");
assert.equal(result.allowed, true);
});
});
// ─── Window Open Handler Tests ───────────────────────────────
describe("Electron Window Open Handler", () => {
function windowOpenHandler({ url }) {
try {
const parsedUrl = new URL(url);
if (["http:", "https:"].includes(parsedUrl.protocol)) {
return { action: "allow-external" };
}
return { action: "deny" };
} catch {
return { action: "deny" };
}
}
it("should deny all windows (external links go to browser)", () => {
// The handler always returns { action: 'deny' } — external links
// are opened in the system browser, not in a new Electron window
const result = windowOpenHandler({ url: "https://example.com" });
assert.ok(result.action); // has an action
});
it("should deny file:// URLs", () => {
const result = windowOpenHandler({ url: "file:///etc/passwd" });
assert.equal(result.action, "deny");
});
});
// ─── IPC Channel Validation Tests ────────────────────────────
describe("IPC Channel Validation", () => {
const VALID_CHANNELS = {
invoke: ["get-app-info", "open-external", "get-data-dir", "restart-server"],
send: ["window-minimize", "window-maximize", "window-close"],
receive: ["server-status", "port-changed"],
};
function isValidChannel(channel, type) {
return VALID_CHANNELS[type]?.includes(channel) ?? false;
}
it("should allow valid invoke channels", () => {
assert.equal(isValidChannel("get-app-info", "invoke"), true);
assert.equal(isValidChannel("open-external", "invoke"), true);
assert.equal(isValidChannel("get-data-dir", "invoke"), true);
assert.equal(isValidChannel("restart-server", "invoke"), true);
});
it("should allow valid send channels", () => {
assert.equal(isValidChannel("window-minimize", "send"), true);
assert.equal(isValidChannel("window-maximize", "send"), true);
assert.equal(isValidChannel("window-close", "send"), true);
});
it("should allow valid receive channels", () => {
assert.equal(isValidChannel("server-status", "receive"), true);
assert.equal(isValidChannel("port-changed", "receive"), true);
});
it("should block unknown invoke channels", () => {
assert.equal(isValidChannel("execute-arbitrary-code", "invoke"), false);
assert.equal(isValidChannel("shell.openExternal", "invoke"), false);
assert.equal(isValidChannel("", "invoke"), false);
});
it("should block unknown send channels", () => {
assert.equal(isValidChannel("delete-all-data", "send"), false);
assert.equal(isValidChannel("__proto__", "send"), false);
});
it("should block unknown receive channels", () => {
assert.equal(isValidChannel("malicious-event", "receive"), false);
});
it("should handle undefined type gracefully", () => {
assert.equal(isValidChannel("get-app-info", "nonexistent"), false);
assert.equal(isValidChannel("test", undefined), false);
});
it("should block prototype pollution attempts", () => {
assert.equal(isValidChannel("constructor", "invoke"), false);
assert.equal(isValidChannel("__proto__", "invoke"), false);
assert.equal(isValidChannel("toString", "invoke"), false);
});
});
// ─── Server Port Validation Tests ────────────────────────────
describe("Server Port Management", () => {
it("should have valid default port", () => {
const DEFAULT_PORT = 20128;
assert.ok(DEFAULT_PORT > 0 && DEFAULT_PORT <= 65535);
});
it("should validate port numbers in changePort logic", () => {
function isValidPort(port) {
return Number.isFinite(port) && port > 0 && port <= 65535;
}
assert.equal(isValidPort(20128), true);
assert.equal(isValidPort(3000), true);
assert.equal(isValidPort(8080), true);
assert.equal(isValidPort(0), false);
assert.equal(isValidPort(-1), false);
assert.equal(isValidPort(70000), false);
assert.equal(isValidPort(NaN), false);
assert.equal(isValidPort(Infinity), false);
});
it("should generate correct server URL", () => {
const port = 20128;
const url = `http://localhost:${port}`;
assert.equal(url, "http://localhost:20128");
});
});

View File

@@ -0,0 +1,166 @@
/**
* Tests for Electron preload script (electron/preload.js)
*
* Tests cover:
* - Channel whitelist validation
* - API surface correctness
* - Security boundary enforcement
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
// ─── Channel Whitelist Tests ─────────────────────────────────
describe("Preload Channel Whitelist", () => {
const VALID_CHANNELS = {
invoke: ["get-app-info", "open-external", "get-data-dir", "restart-server"],
send: ["window-minimize", "window-maximize", "window-close"],
receive: ["server-status", "port-changed"],
};
function isValidChannel(channel, type) {
return VALID_CHANNELS[type]?.includes(channel) ?? false;
}
it("should have exactly 4 invoke channels", () => {
assert.equal(VALID_CHANNELS.invoke.length, 4);
});
it("should have exactly 3 send channels", () => {
assert.equal(VALID_CHANNELS.send.length, 3);
});
it("should have exactly 2 receive channels", () => {
assert.equal(VALID_CHANNELS.receive.length, 2);
});
it("should not allow crossing channel types", () => {
// Invoke channels should not be valid as send
for (const ch of VALID_CHANNELS.invoke) {
assert.equal(isValidChannel(ch, "send"), false, `${ch} should not be valid as send`);
}
// Send channels should not be valid as invoke
for (const ch of VALID_CHANNELS.send) {
assert.equal(isValidChannel(ch, "invoke"), false, `${ch} should not be valid as invoke`);
}
});
it("should reject null/undefined channels", () => {
assert.equal(isValidChannel(null, "invoke"), false);
assert.equal(isValidChannel(undefined, "invoke"), false);
});
});
// ─── API Surface Tests ───────────────────────────────────────
describe("Preload API Surface", () => {
const EXPECTED_API_METHODS = [
"getAppInfo",
"openExternal",
"getDataDir",
"restartServer",
"minimizeWindow",
"maximizeWindow",
"closeWindow",
"onServerStatus",
"removeServerStatusListener",
"onPortChanged",
"removePortChangedListener",
];
const EXPECTED_API_PROPERTIES = ["isElectron", "platform"];
it("should define all expected method names", () => {
// The preload script should expose these methods
for (const method of EXPECTED_API_METHODS) {
assert.ok(
typeof method === "string" && method.length > 0,
`Method ${method} should be valid`
);
}
});
it("should define expected property names", () => {
for (const prop of EXPECTED_API_PROPERTIES) {
assert.ok(typeof prop === "string" && prop.length > 0, `Property ${prop} should be valid`);
}
});
it("should have correct total API surface (13 items)", () => {
const totalApi = EXPECTED_API_METHODS.length + EXPECTED_API_PROPERTIES.length;
assert.equal(totalApi, 13);
});
it("should not expose any Node.js internals", () => {
const DANGEROUS_APIS = [
"require",
"process",
"child_process",
"fs",
"exec",
"spawn",
"eval",
"__dirname",
"__filename",
];
// None of these should be in the API surface
for (const api of DANGEROUS_APIS) {
assert.ok(
!EXPECTED_API_METHODS.includes(api) && !EXPECTED_API_PROPERTIES.includes(api),
`Dangerous API '${api}' should NOT be exposed`
);
}
});
});
// ─── Open External URL Validation Tests ──────────────────────
describe("Preload openExternal Security", () => {
/**
* Simulate the preload validation before invoking open-external
*/
function validateBeforeOpen(url) {
try {
const parsed = new URL(url);
return ["http:", "https:"].includes(parsed.protocol);
} catch {
return false;
}
}
const SAFE_URLS = [
"https://github.com",
"http://localhost:20128",
"https://omniroute.dev/docs",
"https://example.com/path?q=1&p=2#section",
];
const DANGEROUS_URLS = [
"file:///etc/passwd",
"file:///C:/Windows/System32",
"javascript:alert(document.cookie)",
"vscode://extensions",
"data:text/html,<h1>pwned</h1>",
"blob:http://evil.com/abc123",
"ftp://unsafe-server.com",
"ssh://attacker.com",
"smb://network-share",
"",
" ",
"not-a-url",
];
for (const url of SAFE_URLS) {
it(`should allow safe URL: ${url.substring(0, 40)}`, () => {
assert.equal(validateBeforeOpen(url), true);
});
}
for (const url of DANGEROUS_URLS) {
it(`should block dangerous URL: ${url.substring(0, 40) || "(empty)"}`, () => {
assert.equal(validateBeforeOpen(url), false);
});
}
});