Commit Graph

1401 Commits

Author SHA1 Message Date
diegosouzapw
fec6164e92 fix(security): resolve CodeQL alerts #243/#244/#245
#243 (js/request-forgery, high) — providers/bulk/route.ts
- Replace `fetch(\${origin}/api/providers/validate)` (where origin came from
  spoofable `new URL(request.url).origin`) with a direct in-process call to
  validateProviderApiKey. Eliminates the SSRF vector and the HTTP round-trip
  through the same app.
- Resolve proxy once outside the loop and reuse via runWithProxyContext.
- Drop now-unused passthroughAuthHeaders helper.

#244 (js/resource-exhaustion, warn) — copilot-web.ts::solveHashcash
- Clamp upstream-supplied `difficulty` to [1, 8] before `"0".repeat(difficulty)`
  so a malicious/buggy server can't force a huge prefix allocation or push the
  10M-iteration loop into effectively unbounded work.

#245 (js/insufficient-password-hash, warn) — copilot-web.ts::getSession
- Dedupe the inline `createHash("sha256").update(accessToken)` call by reusing
  the existing sessionPoolKey helper.
- Rename its parameter from `accessToken` to `token` and document that the
  input is a high-entropy OAuth bearer used only as an in-memory Map key —
  bcrypt/scrypt/argon2 would be incorrect here, and SHA-256:16 is an
  appropriate fingerprint per docs/security/PUBLIC_CREDS.md.

Tests
- Export solveHashcash and add unit tests asserting it returns null for
  out-of-range / non-integer difficulty and produces a numeric nonce for the
  common difficulty=1 case.
- All 26 tests in copilot-web-executor.test.ts and providers-bulk-route.test.ts
  continue to pass; sessionPoolKey contract (SHA-256:16) preserved.
2026-05-18 23:27:34 -03:00
Paijo
0de964d42b feat(providers): add Gemini Web cookie-based provider (#2380)
Integrated into release/v3.8.0 — adds Gemini Web cookie-based provider with error sanitization fix applied
2026-05-18 22:55:49 -03:00
Diego Rodrigues de Sa e Souza
fa3ee31f06 fix(dashboard): address PR #2384 follow-up review issues (#2389)
- BudgetTab: compute projectionOverBudget per key (HIGH)
- ProviderLimits: convert outer button to div role=button to fix invalid HTML nesting
- Runtime: externalize all strings via next-intl (runtime namespace)
- QuotaShare: externalize all strings via next-intl (quotaShare namespace)
- Add /api/usage/budget/bulk endpoint and switch BudgetTab to single fetch (avoid N+1)

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
2026-05-18 22:52:13 -03:00
diegosouzapw
1c4d850441 fix(build): import Monaco ESM API to fix webpack nls.messages-loader error
Replace import("monaco-editor") with import("monaco-editor/esm/vs/editor/editor.api")
in MonacoEditor.tsx. The root package entry resolves to the minified bundle
(min/vs/editor/editor.main.js) which requires the monaco-editor-webpack-plugin
loader "vs/nls.messages-loader". The ESM API module has no such dependency and
satisfies loader.config({ monaco }) identically.
2026-05-18 19:40:10 -03:00
diegosouzapw
62f609755a fix(types): add explicit cast to silence TS7053 on FREE_PROVIDERS string index
noImplicitAny check rejects FREE_PROVIDERS[resolvedId] because resolveProviderId
returns string but FREE_PROVIDERS has literal key types. Cast to
Record<string, {noAuth?: boolean} | undefined> to express the intent clearly.
2026-05-18 19:30:01 -03:00
diegosouzapw
c9dc205c74 fix(ci): add Zod validation to cli-tools routes and refresh i18n translations for CLAUDE.md
Adds `.safeParse()` to cli-tools/apply and cli-tools/config POST handlers to
satisfy the check:route-validation:t06 CI gate. Regenerates all 41 locale
translations of CLAUDE.md to clear the i18n strict-drift check failure.
2026-05-18 19:19:30 -03:00
Diego Rodrigues de Sa e Souza
eb83eaf25f refactor(dashboard): nav, providers, endpoint, runtime, quota, pricing, budget redesign + quota sharing preview (#2384)
* refactor(dashboard): sidebar subtitles, providers UX, monaco self-host

Sidebar:
- Add subtitleKey to all ~50 menu items so every entry shows a short
  description line (previously only 7 had subtitles).
- Inject 50 new sidebar.*Subtitle keys across all 42 i18n locales
  (en/pt-BR translated; remaining locales fall back to English).
- Fix DATA_÷IC glitch: replace invalid Material Symbols icon
  "data_compression" with "compress" on analytics-compression.
- Rename "Compression Combos" -> "Engine Combos" to avoid truncation
  and disambiguate from the OmniProxy Combos item.

Auto-routing banner:
- Move AutoRoutingBanner from DashboardLayout (rendered on every page)
  to /home only, so it no longer follows the user across the dashboard.

Monaco editor:
- Add shared MonacoEditor wrapper that calls loader.config({ monaco })
  to load Monaco from the bundled monaco-editor package instead of the
  jsdelivr CDN (blocked by CSP script-src 'self'), fixing the
  "Monaco initialization: error" runtime error.
- Migrate the 5 direct dynamic imports of @monaco-editor/react to the
  wrapper (playground, search-tools, translator).

Providers page UX:
- Replace the static legend + 4-stat divider + duplicate filter chips
  with a single row of clickable category pills with embedded counters
  (All, Free, OAuth, API Key, IDE, Compatible, Web Cookie, Search,
  Audio, Local, Cloud Agent).
- Remove redundant per-section Free only / Configured only toggles and
  the Zed Import button from the OAuth section header.
- Introduce a dedicated "IDE Providers" section (Cursor, Zed, Trae)
  rendered under OAuth Providers; exclude IDE_PROVIDER_IDS from OAuth
  to avoid duplication.
- Add zed and trae providers, plus IDE_PROVIDER_IDS set.
- Move the Zed keychain import to /dashboard/providers/zed as a
  contextual Card, instead of cluttering every OAuth section header.
- Extend test-batch route and validation schema with mode: "ide".

* feat(dashboard): add token saver controls to endpoint and context pages

Expose the Token Saver master settings from the endpoint page and
surface its disabled state across the Caveman and RTK context pages.

Update default compression behavior to start disabled with lighter
intensity presets, and add Caveman input compression controls so users
can configure input and output modes separately.

Tighten provider page section rendering by gating compatible, free,
oauth, and api key blocks behind their visibility checks and simplify
several summary labels.

* feat(dashboard): add runtime page and rename limits to quota

Introduce a dedicated runtime observability page for circuit
breakers, cooldowns, lockouts, sessions, and quota alerts.

Split provider quota out of the old limits route, add a redirect from
`/dashboard/limits` to `/dashboard/quota`, and update sidebar/header
labels and i18n keys to match the new navigation.

Enhance provider quota filtering state and remove the tier coverage
widget from the dashboard home view.

* feat(dashboard): redesign budget page and add quota sharing preview

Budget (/dashboard/costs/budget) gets a full rewrite:
- Hero with 6 KPIs (today, month, projected EOM, blocked, at-risk, active)
- Status pills with counts (blocked/alerting/warning/safe/no-limit)
- Templates row (localStorage) with bulk-apply to selected keys
- Multi-key table with checkbox selection and colored progress bar
- Expandable rows with projection card and per-provider cost breakdown
  (last 30d via /api/usage/analytics?apiKeyIds=) plus inline edit form

Quota sharing (/dashboard/costs/quota-share) is new and ships as a beta
UI preview backed by localStorage. Pool persistence in the DB and
request-pipeline enforcement are intentionally deferred to a follow-up.
The page lets operators:
- Create pools from a provider connection + quota window
- Edit allocations per API key with % split and equal-split helper
- Toggle hard/soft/burst policy intent
Donut chart rendered with inline SVG; allocation editor in a modal.

Sidebar gets the new "Quota Sharing" entry under Costs Parameters; i18n
keys propagated across all 41 locales with PT-BR translation.

---------

Co-authored-by: diegosouzapw <diego.souza.pw@gmail.com>
2026-05-18 18:53:00 -03:00
diegosouzapw
04d44f6262 fix(security): sanitize error messages, fix ReDoS patterns, harden OAuth callback
Error message sanitization (Hard Rule #12):
- claude-auth/export, codex-auth/export, gemini-cli-auth/export routes: replace
  raw err.message with sanitizeErrorMessage() from open-sse/utils/error.ts
- imageGeneration, musicGeneration, videoGeneration handlers: import
  sanitizeErrorMessage and replace all err.message in return values
- veoaifree-web executor: replace raw upstream response data in errResp() calls
  with static strings

OAuth callback page (callback/page.tsx):
- Remove useSearchParams/Suspense dependency that caused hydration failures in
  popup windows navigating back from Google OAuth (COOP header severs opener)
- Use window.location.search directly in useEffect with three send methods:
  postMessage, BroadcastChannel, localStorage
- Fix postMessage target from "*" to window.location.origin (semgrep finding)
- Move setCurrentUrl call to manual-only branch to avoid unnecessary renders

copilot-web executor:
- Move accessToken from WebSocket URL query string to Authorization header
  (avoids credential exposure in server logs)
- Add MAX_POOL_SIZE=100 cap to sessionPool with LRU eviction of oldest entry

CodeQL ReDoS fixes (js/polynomial-redos #233-240):
- Replace while(s.endsWith("/")) s=s.slice(0,-1) pattern (O(n²) allocations)
  with index-based loop (O(n) time, single final slice) in:
  bin/cli/api.mjs, all 6 cli-helper config generators, opencode-provider

Gemini OAuth:
- mapTokens: add idToken field to fix "missing id_token" export error
2026-05-18 17:42:09 -03:00
diegosouzapw
5ce3f7f4d6 chore: merge release/v3.8.0 into PR branch 2026-05-18 15:48:43 -03:00
diegosouzapw
5098d8fd7e chore: resolve conflicts with release/v3.8.0 2026-05-18 15:34:49 -03:00
diegosouzapw
b7316d56e8 style(providers): reformat provider validation calls for readability
Wrap long function signatures and validation requests across multiple
lines to match project formatting conventions and improve scanability.
2026-05-18 14:33:56 -03:00
diegosouzapw
5ed96f5072 fix(routing): implement embedding combos, local provider validation bypass, and resolve migration collisions 2026-05-18 14:33:56 -03:00
oyi77
55160bc52a feat(content): add Haiper, Leonardo, Ideogram, Suno, Udio providers
5 new content generation providers with full handler implementations:

Video: haiper (gen2), leonardo (phoenix)
Image: haiper (gen2), leonardo (phoenix/sdxl), ideogram (V3/V2A)
Music: suno (chirp-v3-5/v4), udio (web wrapper)

All async handlers: 5s poll interval, 5 min timeout (60 polls).
Auth: API key (haiper/leonardo/ideogram), cookie (suno/udio).

Closes #2376

Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
2026-05-18 23:41:27 +07:00
diegosouzapw
b17fd87470 fix(rate-limiter): Redis is now opt-in via REDIS_URL (#2357)
Docker images launched without a sibling Redis container used to spam
'[REDIS] Error: connect ECONNREFUSED 127.0.0.1:6379' for every
rate-limit and API-key-auth cache lookup. The root cause was a default
of process.env.REDIS_URL || 'redis://localhost:6379' that turned the
opt-in cache into a hard dependency.

Three coordinated changes:

1. src/shared/utils/rateLimiter.ts — gate Redis on REDIS_URL being
   explicitly set. getRedisClient() returns null when disabled; the
   single connection-error handler dedupes via a redisErrorLogged latch
   so a sustained outage produces one warn instead of per-request flood.
   checkRateLimit() routes to the existing in-memory store on both the
   'disabled' and 'test' paths.

2. src/lib/db/apiKeys.ts — short-circuit Redis-backed auth cache reads
   and writes when getRedisClient() returns null. SQLite remains
   authoritative; the cache is purely an optimization.

3. Same file — replace the wildcard scope matcher's dynamic RegExp
   compilation with a deterministic segment walker. Eliminates the
   ReDoS surface on operator-supplied scope patterns and silences the
   Semgrep js/regex-injection advisory that previously blocked edits to
   this file.

Single-instance deployments now work silently out of the box;
multi-instance setups continue to use Redis when REDIS_URL is set.
2026-05-18 10:57:23 -03:00
diegosouzapw
f2e368830a fix(combo): guard target.modelStr against non-string before .startsWith (#2359)
Combo dispatch and the combo test button used to crash with
'TypeError: e.startsWith is not a function' when a step's modelStr
failed to resolve (regression after #2338 added per-account LKGP
routing for local/Docker providers). The TypeScript annotation says
the field is always a string, but malformed combo rows leaked through
to the dispatch path.

Two defensive boundary guards:

1. open-sse/services/combo.ts LKGP fallback findIndex — type-check
   target.modelStr before calling startsWith.

2. src/app/api/combos/test/route.ts testComboTarget — coerce
   target.modelStr at the entry, surface a clean 'Combo step is
   missing a model id' error instead of crashing.

Regression test parses the source and asserts no unguarded
target.modelStr.<string-method> usages remain in combo.ts, so a
future refactor that reintroduces the pattern fails loudly.
2026-05-18 10:56:27 -03:00
Mrinal Joshi
400dbc386a fix(claude-oauth): enable system-transforms pipeline for native claude executor
The native claude OAuth path (base.ts:794 applySystemTransformPipeline)
early-exited because DEFAULT_SYSTEM_TRANSFORMS_CONFIG.providers[claude]
was {enabled:false, pipeline:[]}. Result: third-party-agent fingerprints
(github.com/anomalyco/opencode, 'You are OpenCode', 'Here is some useful
information about the environment...') leaked into /v1/messages system
blocks, triggering Anthropic billing-gate:
  [400] Third-party apps now draw from extra usage, not plan limits.

Mirror the cc-bridge defaults:
- DEFAULT_PARAGRAPH_REMOVAL_ANCHORS + OPENWEBUI_PARAGRAPH_ANCHORS
- DEFAULT_IDENTITY_PREFIXES   + OPENWEBUI_IDENTITY_PREFIXES
- DEFAULT_TEXT_REPLACEMENTS as replace_text ops (allOccurrences:true)
- obfuscate_words

Omit prepend_system_block + inject_billing_header — base.ts:759-784
already handles those on the native claude OAuth path.

UI mirror RoutingTab.tsx and snapshot in system-transforms.test.ts
updated for parity. 56/56 transform-related tests pass.

Verified end-to-end 2026-05-18 after npm run build:cli + restart:
- request v8tq04 -> 200 in 1.84s
- request shape {max_tokens:32000, reasoning_effort:high} -> 200 in 1.99s
- x-omniroute-provider=cc (claude-code OAuth pool, account 62875e01)
- telemetry: [SystemTransforms] claude-native: drop_paragraph_if_contains,
  drop_paragraph_if_starts_with, replace_text, replace_text, obfuscate_words
2026-05-18 13:25:30 +01:00
diegosouzapw
33928afec0 fix(ui/tooltip): render in portal + clamp to viewport (#2352)
When the shared <Tooltip> appeared inside a modal (combo editor) or any
ancestor with overflow:hidden/auto, long labels were clipped — the
absolute-positioned <span> stayed inside the modal's stacking context.

Render the tooltip via createPortal to document.body by default
(usePortal prop, defaults to true). Coordinates are computed from the
trigger's getBoundingClientRect on each show and written directly to
the tooltip ref's .style — that avoids the cascading-render warning
that setState-inside-effect triggers, while still doing one synchronous
measure-and-position pass before the user sees any flicker.

Coordinates are clamped to the viewport bounds so a trigger near the
right edge produces a tooltip that stays on-screen instead of bleeding
off. Adds an optional multiline prop that swaps the legacy
whitespace-nowrap clamp for max-w-xs whitespace-normal break-words for
explanation strings (combo strategy help, etc.).

Backward compat: existing call sites do not need to change; the portal
+ clamp behavior is transparent to consumers.
2026-05-18 09:18:25 -03:00
diegosouzapw
5da9b1b778 fix(auto-routing): replace bare getSettings() with getCachedSettings (#2346)
The auto-routing path in src/sse/handlers/chat.ts called `getSettings()`
on every `auto` / `auto/\*` request, but the symbol was never imported in
this module (only `getCachedSettings` was). The runtime ReferenceError
crashed the request with a 500 before any routing could happen.

Replace the call with `getCachedSettings` (already imported, identical
return shape, plus the cached variant benefits the auto-routing hot path).
A regression-guard test scans chat.ts to fail loudly if a future refactor
re-introduces the unimported call pattern.
2026-05-18 09:08:52 -03:00
Paijo
c4fe3d63be feat(content): extend providers with video, audio, TTS, music capabilities (#2369)
Integrated into release/v3.8.0
2026-05-18 08:37:44 -03:00
Paijo
dd24571949 feat(providers): add Veo AI Free as web wrapper provider (#2366)
Integrated into release/v3.8.0
2026-05-18 08:35:34 -03:00
Paijo
41b7f13b27 feat(providers): add Replicate as free provider (#2364)
Integrated into release/v3.8.0
2026-05-18 08:34:00 -03:00
diegosouzapw
2bbd0fff45 Merge feat/gemini-auth-ui (PR3 Gemini) into release/v3.8.0
# Conflicts:
#	src/app/(dashboard)/dashboard/providers/[id]/page.tsx
#	src/i18n/messages/en.json
2026-05-18 03:21:56 -03:00
diegosouzapw
1dae73cfef Merge feat/gemini-auth-api-routes (PR2 Gemini) into release/v3.8.0
# Conflicts:
#	src/shared/validation/schemas.ts
2026-05-18 03:01:51 -03:00
diegosouzapw
f67c7d9383 Merge feat/gemini-auth-libs (PR1 Gemini) into release/v3.8.0 2026-05-18 02:58:51 -03:00
diegosouzapw
855dc86ea8 fix(dashboard): add missing comma in Gemini i18n JSON (PR3) 2026-05-18 02:45:51 -03:00
diegosouzapw
b3cb5b68d8 feat(dashboard): add Gemini CLI auth import/export UI + i18n (PR3) 2026-05-18 02:43:15 -03:00
diegosouzapw
75c1d2ead2 feat(dashboard): add Claude Code auth import/export UI + i18n (PR3) 2026-05-18 02:26:08 -03:00
diegosouzapw
877aafdb99 feat(api): add Gemini CLI auth import/export API routes + schemas (PR2) 2026-05-18 01:46:07 -03:00
diegosouzapw
3786e929d6 feat(api): add Claude Code auth import/export API routes + schemas (PR2) 2026-05-18 01:45:54 -03:00
diegosouzapw
c6a51605ce feat(oauth): add Gemini CLI auth import/export libs + CLI_TOOLS registration (PR1) 2026-05-18 01:12:44 -03:00
diegosouzapw
09fb5cdf34 feat(oauth): add Claude Code auth import/export libs + tests (PR1) 2026-05-18 01:07:38 -03:00
Cong Vu Chi
6d4ee4f85a fix: allow bracketed combo names (#2354)
Integrated into release/v3.8.0
2026-05-17 22:58:42 -03:00
Paijo
1be18f5530 feat(providers): add Microsoft Copilot Web wrapper (#2340)
Integrated into release/v3.8.0 — fixed session pool security issue (per-token isolation) and added unit tests
2026-05-17 20:02:02 -03:00
Paijo
193e7a6417 feat(routing): LGKP remembers last good account per provider (#2338)
Integrated into release/v3.8.0 — added unit tests for connectionId-aware getLKGP/setLKGP
2026-05-17 19:54:32 -03:00
Michael
b191173ae1 Fix Providers empty state blocking first provider setup 2026-05-17 19:47:42 -03:00
backryun
1f27c344d4 chore: improve huggingface provider support (#2322)
Integrated into release/v3.8.0 — migrates HuggingFace to router.huggingface.co/v1 endpoint, adds dynamic model discovery, refreshes ASR/TTS catalog
2026-05-17 19:44:53 -03:00
Paijo
aefd9bbbc7 feat(providers): add Hackclub AI as free provider (#2339)
Integrated into release/v3.8.0 — adds Hackclub AI as free aggregator with 30+ models, auth optional
2026-05-17 19:43:58 -03:00
Paijo
c2451038a0 feat(providers): add GitHub Models as free provider (#2344)
Integrated into release/v3.8.0 — adds GitHub Models as a free provider with 14 models (GPT-4.1, o3, DeepSeek-R1, Llama 4, Grok 3 etc.)
2026-05-17 19:40:50 -03:00
Diego Rodrigues de Sa e Souza
891a9e4125 Merge pull request #2337 from diegosouzapw/worktree-fix+providers-missing-button-i18n
fix(providers): fix missing i18n keys and add 'Add Provider' button to empty state
2026-05-17 17:51:43 -03:00
diegosouzapw
3748b0238d chore(merge): resolve conflicts for bulk import — keep both schemas and all i18n keys 2026-05-17 17:28:41 -03:00
diegosouzapw
3227c9ffe3 chore(merge): resolve page.tsx conflict — keep ImportCodexAuthModal and ApplyCodexAuthModal 2026-05-17 17:13:50 -03:00
diegosouzapw
25f3fe1ac5 feat(codex): bulk import Codex auth.json — multi-file, paste, ZIP
Adds three input modes for importing multiple Codex accounts at once,
all feeding a single partial-failure backend endpoint.

- `codexAuthZipExtract.ts`: safe ZIP extraction via fflate — rejects
  path traversal (../ and absolute paths), per-file 256 KB cap, 10 MB
  total cap, max 50 .json entries
- `POST /api/providers/codex-auth/zip-extract`: server-side ZIP
  extraction returning [{name, json, parseError}] to the client
- `POST /api/providers/codex-auth/import-bulk`: iterates entries,
  partial-failure semantics (always 200), per-entry audit log
  `provider.credentials.imported` + summary `bulk_imported`
- `importCodexAuthBulkSchema`: max 50 entries, email validation
- `<ImportCodexAuthModal>`: Single/Bulk top tabs; Bulk has Upload
  files, Paste list (JSON array or --- separator), ZIP sub-modes;
  live entry preview list; overwrite checkbox; result panel
- Install `fflate@0.8.3` (pure TypeScript, zero native deps)
- 29 unit tests: 12 ZIP safety cases + 17 schema/parser/shape cases
2026-05-17 16:52:43 -03:00
diegosouzapw
8a6d681c15 feat(codex): import single Codex auth.json as OAuth connection
Adds an import flow that lets users bring an existing Codex auth.json
into OmniRoute without a fresh OAuth login. Both a file-upload tab and
a paste-JSON tab are supported.

- `codexAuthImport.ts`: pure parser + createConnectionFromAuthFile
  (conflict detection, overwriteExisting, JWT email/exp extraction)
- `POST /api/providers/codex-auth/import`: Zod-validated endpoint with
  audit log (`provider.credentials.imported`)
- `importCodexAuthSchema` in schemas.ts (discriminated union json/text,
  256 KB cap on paste source)
- `<ImportCodexAuthModal>` in providers/[id]/page.tsx with upload/paste
  tabs, email auto-detection, name/email/overwrite fields
- "Import auth" toolbar button shown only on the Codex provider page
- 29 unit tests (17 parser + 12 schema) — all passing
2026-05-17 14:04:48 -03:00
diegosouzapw
de5434a0a6 fix(providers): fix missing i18n keys and add 'Add Provider' button to empty state
- Add addFirstProvider, addFirstProviderDesc, learnMore to providers namespace in en.json
  (keys existed only in common namespace, causing raw key display on fresh installs)
- Add primary 'Add Provider' button to empty state that reveals provider grid
  (only 'Learn more' external link existed, leaving users with no way to add a provider)
- Remove || fallback strings now that i18n keys are correctly placed
2026-05-17 13:53:06 -03:00
diegosouzapw
634f50a04e feat(codex-auth): rename export to auth-{email}.json and gate Apply Local behind confirmation modal
Export filename change:
- Drop the redundant `codex-` prefix; embed the account email so multiple
  exported files can coexist in the same downloads folder.
- Email is extracted from the id_token JWT `email` claim, with fallback
  to connection.email and finally to the sanitized connection label.
- sanitizeFileNamePart now preserves @ so addresses survive intact
  (e.g. `auth-diego@example.com.json`).

Apply Local refinement:
- ApplyCodexAuthModal: confirmation modal showing the resolved target
  path, the side-by-side .bak location, and the centralized backup
  trail. User must tick a confirmation checkbox before Apply enables.
- writeCodexAuthFileToLocalCli now writes a side-by-side
  `auth-<timestamp>.bak` inside the .codex/ directory before replacing
  the live file, in addition to the existing centralized backup. Both
  inputs to the .bak path are server-controlled (dirname from the
  static CLI_TOOLS table; basename from a server-generated ISO
  timestamp), so no user input touches path APIs.
- apply-local route now emits a `provider.credentials.applied` audit
  event with the resolved authPath and savedBakPath, and routes all
  errors through sanitizeErrorMessage() per the security guide.

Tests: tests/unit/codexAuthFile.test.ts covers sanitization, JWT email
extraction, filename format for both branches (email/label), and the
ISO-timestamp .bak basename safety.

Scope: this is PR1 of the import/export work tracked under
_tasks/features-v3.8.0/importexport/. PR2 (import single) and PR3
(import bulk) will follow.
2026-05-17 13:32:29 -03:00
diegosouzapw
fc9f8d91f7 feat(providers): bulk add API keys with Single/Bulk tabs
Mirrors the 9router UX (one textarea, name|apiKey per line) but goes
further: dedicated server-side endpoint with Zod validation, partial-
failure semantics, audit log, and provider whitelist.

UI (src/app/(dashboard)/dashboard/providers/[id]/page.tsx):
- AddApiKeyModal now switches between Single (existing behaviour) and
  Bulk Add via tab strip. Tabs hide for providers that don't support
  bulk (Vertex, web-session, OAuth, multi-field).
- Bulk pane: textarea, shared Priority + "validate each key" checkbox,
  result panel with per-line errors (truncated at 10).

Backend:
- POST /api/providers/bulk: iterates entries through createProviderConnection
  with the same provider-specific normalization as the single endpoint.
  Returns {success, failed, total, created, errors[]}. Optional pre-save
  validation via /api/providers/validate when validateKeys=true. Each
  entry succeeds/fails independently — no transaction rollback.
- Bulk audit event logged once per request plus per-entry success events.

Schemas:
- bulkCreateProviderSchema (src/shared/validation/schemas.ts): max 200
  entries, mandatory name+apiKey per entry, google-pse-search cx guard.
- supportsBulkApiKey() helper (src/shared/constants/providers.ts) with
  explicit deny-list for OAuth/web-session/multi-field providers.

Parser:
- parseBulkApiKeys() (src/shared/utils/bulkApiKeyParser.ts) handles
  CRLF, # comments, blank lines, pipe inside apiKey, empty-name fallback,
  and caps input at BULK_API_KEY_MAX_LINES (200) with a warning.

Tests:
- tests/unit/bulkApiKeyParser.test.ts: 12 cases (format, edge cases, cap)
- tests/unit/providers-bulk-route.test.ts: 12 cases (schema, whitelist,
  response shape, apiKey leak guard)

i18n:
- en.json: bulkTabSingle, bulkTabBulkAdd, bulkAddFormatHint,
  bulkValidateKeys, bulkAddAllKeys, bulkAddedCount, bulkFailedCount,
  adding
2026-05-17 11:30:49 -03:00
diegosouzapw
a45d9190db fix(security): resolve CodeQL ReDoS + URL sanitization alerts
- Replace replace(/\/+$/, "") with explicit while-endsWith loop to avoid
  polynomial backtracking on inputs with repeated trailing slashes
  (CodeQL js/polynomial-redos #233-240, 8 alerts):
  - @omniroute/opencode-provider/src/index.ts (normalizeBaseURL)
  - bin/cli/api.mjs (stripTrailingSlash)
  - src/lib/cli-helper/config-generator/{claude,cline,codex,continue,
    kilocode,opencode}.ts (6 generators with identical pattern)

- tests/live/deepseek-web-live.test.ts: assert hostname via URL parsing
  instead of String.includes() so the check is exact-match rather than
  substring (CodeQL js/incomplete-url-substring-sanitization #241).

Alert #242 (Array.prototype.includes against fixed needle constant
OPENWEBUI_PARAGRAPH_ANCHORS) dismissed as CodeQL false-positive — not a
URL sanitization callsite.
2026-05-17 07:43:32 -03:00
diegosouzapw
ca8f492240 fix(i18n): add missing providers.{allProviders,audioProviders,showFreeOnly} keys
PR #2314's category filter chips and free-only toggle on the providers
page reference these keys under the 'providers' namespace, but the keys
exist only under 'common'. Add them under 'providers' so the chips render
without 'MISSING_MESSAGE' console errors.
2026-05-17 05:51:57 -03:00
diegosouzapw
440ca8e3cc Merge pull request #2314 from oyi77/feat/gitlawb-opengateway
feat: gitlawb opengateway provider + providers page filter chips

Three independent contributions from oyi77 bundled in this PR:

1. Gitlawb Opengateway provider (b83d1a0fc):
   - gitlawb (alias glb) — xiaomi-mimo endpoint with 5 MiMo models
   - gitlawb-gmi (alias glb-gmi) — gmi-cloud endpoint with 40+ models
   - Both flagged free tier, CLI-mimicking headers to avoid upstream
     rate limiting
   - 9 unit tests in tests/unit/gitlawb-provider.test.ts (all green)

2. hasFree flag (d7dcd233a): friendliai, chutes, featherless-ai now
   correctly surface free tier badge in the providers page.

3. UI additions (debf7cb28):
   - CollapsibleSection.tsx (coexists with our existing Collapsible.tsx —
     different API for different use cases)
   - Category filter chips bar on /dashboard/providers (auto-merged)
   - Free-only toggle on /dashboard/providers (auto-merged)
   - Extended filterConfiguredProviderEntries with showFreeOnly param
   - i18n tooltip keys for Caveman/RTK settings (union with our
     simpleMode/advancedMode/filterCatalog keys)
   - InfoTooltip + PresetSlider identical to PR #2316 versions
     (silent dedup by auto-merge)

Conflict resolution:
- src/shared/components/index.tsx: union — added CollapsibleSection
  export alongside our NoAuthProviderCard.
- src/i18n/messages/en.json: union — kept all our keys from #2316
  (simpleMode/advancedMode/filterCatalog/filterCatalogDesc) and added
  PR's tooltip keys (searchFilters, tooltipDedup, tooltipMaxChars,
  tooltipMaxLines, tooltipAutoTrigger, tooltipCompressionRate,
  tooltipMaxTokens, tooltipMinLength, tooltipMinSavings, ultraSettings,
  ultraSettingsDesc).

Closes #2314
2026-05-17 02:37:05 -03:00
diegosouzapw
43d4ea092c Merge pull request #2316 from oyi77/feat/ui-rework
feat(ui): simple/advanced mode for Caveman & RTK + newbie UX improvements

Adapts oyi77's UX rework on top of our refactor/pages overhaul. Layout
priority: our 9-section sidebar restructure stays; PR's additions
(subtitles, intros, simple/advanced toggles, empty states, error labels)
are integrated into our structure.

Conflict resolution:

- index.tsx: union — exports InfoTooltip, PresetSlider (new shared
  components from PR) alongside our NoAuthProviderCard.
- en.json: union — kept our "OmniSkills"/"AgentSkills" labels and
  "API Key Manager" naming; added PR's subtitleKey strings, settings
  intro keys, and empty state keys.
- sidebarVisibility.ts: kept our 9-section structure; added subtitleKey?:
  string to SidebarItemDefinition and mapped subtitle keys onto the 7
  matching items (endpoints, api-manager, combos, batch, context-caveman,
  context-rtk, webhooks).
- Sidebar.tsx: kept our collapsible-section rendering; integrated PR's
  subtitle support into resolveItem() and renderNavLink() label area.
- CavemanContextPageClient.tsx: took PR's version — adds SegmentedControl
  for simple/advanced mode (gates full settings tab in advanced).
- RtkContextPageClient.tsx: took PR's version — adds SegmentedControl +
  Collapsible filter catalog.
- settings/page.tsx: kept our redirect (we converted tabs→pages). Ported
  PR's intro text paragraphs to /settings/ai, /settings/routing, and
  /settings/resilience subpages using the auto-merged i18n keys.
- HomePageClient.tsx: kept ours — we removed Providers Overview card in
  the refactor, and PR's empty state for that card is now redundant.
  PR's equivalent empty state at /dashboard/providers (in
  providers/page.tsx) auto-merged cleanly and serves the same purpose.

Closes #2316
2026-05-17 02:13:22 -03:00