diff --git a/.agent/workflows/git-workflow.md b/.agent/workflows/git-workflow.md deleted file mode 100644 index 5d718a7678..0000000000 --- a/.agent/workflows/git-workflow.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -description: Git workflow — NEVER commit directly to main. Always use feature branches. ---- - -# Git Workflow - -## ⚠️ CRITICAL RULE: NEVER commit directly to `main` - -## Steps - -1. **Before starting any work**, create a feature branch from `main`: - - ```bash - git checkout main && git pull origin main - git checkout -b feature/ - ``` - -2. **During development**, commit to the feature branch: - - ```bash - git add -A && git commit -m "(): " - ``` - -3. **Before pushing**, verify the build passes: - - ```bash - npm run build - ``` - -4. **When the feature is complete and verified**, push the branch and STOP: - - ```bash - git push origin feature/ - ``` - -5. **DO NOT** create a PR, merge, or push to `main`. Let the user handle that. - -## Branch naming convention - -- `feature/` — new features -- `fix/` — bugfixes -- `refactor/` — refactoring -- `docker/` — Docker / infrastructure changes -- `style/` — UI / CSS changes - -## Commit types - -- `feat` — new feature -- `fix` — bugfix -- `refactor` — code refactoring -- `style` — UI / CSS changes -- `docker` — Docker / infrastructure -- `docs` — documentation -- `chore` — maintenance diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f7594904..dca457ae6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [1.0.8] — 2026-02-21 + +> ### 🔒 API Security & Windows Support +> +> Adds API Endpoint Protection for `/models`, Windows server startup fixes, and UI improvements. + +### ✨ New Features + +- **API Endpoint Protection (`/models`)** — New Security Tab settings to optionally require an API key for the `/v1/models` endpoint (returns 404 when unauthorized) and to selectively block specific providers from appearing in the models list ([#100](https://github.com/diegosouzapw/OmniRoute/issues/100), [#96](https://github.com/diegosouzapw/OmniRoute/issues/96)) +- **Interactive Provider UI** — Blocked Providers setting features an interactive chip selector with visual badges for all available AI providers + +### 🐛 Bug Fixes + +- **Windows Server Startup** — Fixed `ERR_INVALID_FILE_URL_PATH` crash on Windows by safely wrapping `import.meta.url` resolution with a fallback to `process.cwd()` for globally installed npm packages ([#98](https://github.com/diegosouzapw/OmniRoute/issues/98)) +- **Combo buttons visibility** — Fixed layout overlap and tight spacing for the Quick Action buttons (Clone / Delete / Test) on the Combos page on narrower screens ([#95](https://github.com/diegosouzapw/OmniRoute/issues/95)) + +--- + ## [1.0.7] — 2026-02-20 > ### 🐛 Bugfix Release — OpenAI Compatibility, Custom Models & OAuth UX diff --git a/README.md b/README.md index 6a0bc0fab5..8d4c466b82 100644 --- a/README.md +++ b/README.md @@ -373,6 +373,7 @@ Access via: WhatsApp, Telegram, Slack, Discord, iMessage, Signal... | 🔒 **TLS Fingerprint Spoofing** | Bypass TLS-based bot detection via wreq-js | | 🌐 **IP Filtering** | Allowlist/blocklist for API access control | | 📊 **Editable Rate Limits** | Configurable RPM, min gap, and max concurrent at system level | +| 🛡 **API Endpoint Protection** | Auth gating + provider blocking for the `/models` endpoint | ### 📊 Observability & Analytics diff --git a/README.pt-BR.md b/README.pt-BR.md index d2e1bcbd1f..923204f51d 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -373,6 +373,7 @@ Acesso via: WhatsApp, Telegram, Slack, Discord, iMessage, Signal... | 🔒 **Spoofing de Fingerprint TLS** | Bypass de detecção de bot via TLS com wreq-js | | 🌐 **Filtragem de IP** | Allowlist/blocklist para controle de acesso à API | | 📊 **Rate Limits Editáveis** | RPM, gap mínimo e concorrência máxima configuráveis | +| 🛡 **Proteção de Endpoint API** | Gateway de Auth + bloqueio de provedores para o endpoint `/models` | ### 📊 Observabilidade e Analytics diff --git a/docs/FEATURES.md b/docs/FEATURES.md index d2bd088b79..d98dd70aae 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -46,7 +46,7 @@ Four modes for debugging API translations: **Playground** (format converter), ** ## ⚙️ Settings -General settings, system storage, backup management (export/import database), appearance (dark/light mode), security, routing, resilience, and advanced configuration. +General settings, system storage, backup management (export/import database), appearance (dark/light mode), security (includes API endpoint protection and custom provider blocking), routing, resilience, and advanced configuration. ![Settings Dashboard](screenshots/06-settings.png) diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 2cf009be87..4b3fc42b09 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -610,7 +610,7 @@ The settings page is organized into 5 tabs for easy navigation: | Tab | Contents | | -------------- | ---------------------------------------------------------------------------------------------- | -| **Security** | Login/Password settings and IP Access Control (allowlist/blocklist) | +| **Security** | Login/Password settings, IP Access Control, API auth for `/models`, and Provider Blocking | | **Routing** | Global routing strategy (6 options), wildcard model aliases, fallback chains, combo defaults | | **Resilience** | Provider profiles, editable rate limits, circuit breaker status, policies & locked identifiers | | **AI** | Thinking budget configuration, global system prompt injection, prompt cache stats | diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index 3dcda4d1ab..acf0e8b093 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -409,14 +409,14 @@ function ComboCard({ {/* Actions */} -
+
-
+
+ ); + })} +
+ {blockedProviders.length > 0 && ( +

+ warning + {blockedProviders.length} provider{blockedProviders.length !== 1 ? "s" : ""} blocked + from /models +

+ )} +
+
+ +
diff --git a/src/app/api/v1/models/route.ts b/src/app/api/v1/models/route.ts index 4fe9bf1fe8..ad39b6df06 100644 --- a/src/app/api/v1/models/route.ts +++ b/src/app/api/v1/models/route.ts @@ -1,7 +1,8 @@ import { CORS_ORIGIN } from "@/shared/utils/cors"; import { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models"; import { AI_PROVIDERS } from "@/shared/constants/providers"; -import { getProviderConnections, getCombos, getAllCustomModels } from "@/lib/localDb"; +import { getProviderConnections, getCombos, getAllCustomModels, getSettings } from "@/lib/localDb"; +import { extractApiKey, isValidApiKey } from "@/sse/services/auth"; import { getAllEmbeddingModels } from "@omniroute/open-sse/config/embeddingRegistry.ts"; import { getAllImageModels } from "@omniroute/open-sse/config/imageRegistry.ts"; import { getAllRerankModels } from "@omniroute/open-sse/config/rerankRegistry.ts"; @@ -94,10 +95,28 @@ export async function OPTIONS() { * GET /v1/models - OpenAI compatible models list * Returns models from all active providers, combos, embeddings, and image models in OpenAI format */ -export async function GET() { +export async function GET(request: Request) { try { + // Issue #100: Optionally require API key for /models (security hardening) + // When enabled, unauthenticated requests get 404 to hide endpoint existence + let settings: Record = {}; + try { + settings = await getSettings(); + } catch {} + if (settings.requireAuthForModels === true) { + const apiKey = extractApiKey(request); + if (!apiKey || !(await isValidApiKey(apiKey))) { + return new Response("Not Found", { status: 404 }); + } + } + const { aliasToProviderId, providerIdToAlias } = buildAliasMaps(); + // Issue #96: Allow blocking specific providers from the models list + const blockedProviders: Set = new Set( + Array.isArray(settings.blockedProviders) ? settings.blockedProviders : [] + ); + // Get active provider connections let connections = []; let totalConnectionCount = 0; // Track if DB has ANY connections (even disabled) @@ -150,6 +169,9 @@ export async function GET() { const providerId = aliasToProviderId[alias] || alias; const canonicalProviderId = FALLBACK_ALIAS_TO_PROVIDER[alias] || providerId; + // Skip blocked providers (Issue #96) + if (blockedProviders.has(alias) || blockedProviders.has(canonicalProviderId)) continue; + // Only include models from providers with active connections if (!activeAliases.has(alias) && !activeAliases.has(canonicalProviderId)) { continue; diff --git a/src/lib/db/migrationRunner.ts b/src/lib/db/migrationRunner.ts index d8fbe7a71b..b18a8e7047 100644 --- a/src/lib/db/migrationRunner.ts +++ b/src/lib/db/migrationRunner.ts @@ -14,9 +14,26 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import type Database from "better-sqlite3"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const MIGRATIONS_DIR = path.join(__dirname, "migrations"); +/** + * Resolve the migrations directory path safely across platforms. + * On Windows with global npm installs, `import.meta.url` may not be a valid + * `file://` URL, causing `fileURLToPath` to throw `ERR_INVALID_FILE_URL_PATH`. + */ +function resolveMigrationsDir(): string { + try { + const metaUrl = import.meta.url; + if (metaUrl && metaUrl.startsWith("file://")) { + const __filename = fileURLToPath(metaUrl); + return path.join(path.dirname(__filename), "migrations"); + } + } catch { + // fileURLToPath failed (e.g. Windows global install) — use fallback + } + // Fallback: resolve relative to cwd (works for both dev and global installs) + return path.join(process.cwd(), "src", "lib", "db", "migrations"); +} + +const MIGRATIONS_DIR = resolveMigrationsDir(); /** * Ensure the schema_migrations tracking table exists.