Commit Graph

877 Commits

Author SHA1 Message Date
diegosouzapw
10c8f32bd9 fix(security): drop hashing in sessionPoolKey to clear CodeQL #247
CodeQL re-flagged the HMAC variant from #2394 at high severity (#247) —
its data-flow analysis still sees an OAuth bearer reaching .update(token)
and applies js/insufficient-password-hash, regardless of whether the hash
primitive is createHash or createHmac.

Stop hashing the token at all. The session-pool Map is keyed by the
token verbatim, falling back to "anonymous" when the input is missing
or empty. This is safe because:

  - The token is already held in CopilotSession.cookies for every pool
    entry, so the Map key adds no new in-memory exposure.
  - The pool is bounded by MAX_POOL_SIZE with LRU eviction, so memory
    stays bounded regardless of how many distinct tokens appear.
  - bcrypt/scrypt/argon2 — the only forms CodeQL accepts here — are the
    wrong tool, since their slowness exists to thwart brute-force of
    low-entropy human passwords we do not have.

Tests
- Replace the "16-char hex" shape assertion with verbatim-equality
  assertions and an explicit "empty string → anonymous" case.
- Keep the regression guard that fails if anyone ever re-introduces
  createHash/createHmac on the token (catches the alert reappearing
  before CodeQL does).
- 16/16 copilot-web tests pass.
2026-05-19 00:26:17 -03:00
diegosouzapw
6fcc99ba26 fix(security): switch sessionPoolKey to HMAC to clear CodeQL #246
CodeQL re-flagged the SHA-256 inside sessionPoolKey at high severity even
after the rename/dedup in #2391 — its data-flow analysis still tracks the
OAuth bearer (accessToken → token) into createHash and applies the
js/insufficient-password-hash rule. Bcrypt/scrypt/argon2 would be wrong
here (the input is a high-entropy bearer, not a low-entropy human password
that needs brute-force protection).

Switch to HMAC-SHA-256 with a process-scope key generated at startup
(randomBytes(32)). HMAC is a MAC primitive, not a password hash, so the
CodeQL rule no longer applies; uniqueness, determinism-within-process,
and the 16-char hex shape all still hold, and as a small bonus an
off-process attacker can no longer precompute pool keys from a token
alone.

Tests
- Replace the "exact SHA-256 prefix" assertion with shape/uniqueness
  checks and a regression guard that fails if the implementation ever
  reverts to plain createHash.
- All 15 copilot-web tests pass.
2026-05-18 23:53:16 -03:00
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
backryun
f43badc3d4 model: Add Composer 2.5 to Cursor Provider (#2381)
Integrated into release/v3.8.0 — adds Composer 2.5 models to Cursor provider and updates CLI fingerprints
2026-05-18 22:53:27 -03:00
oyi77
44d9abac96 fix: add tool_use/tool_result adjacency guard for Claude API
Claude requires tool_result in the IMMEDIATELY next message after tool_use.
Existing fixToolPairs() checks global ID presence but not adjacency —
keeping tool_use blocks whose tool_result exists later in the array but
not in the next message, causing 400 errors.

Add fixToolAdjacency() that runs after fixToolPairs() to remove tool_use
blocks where the next message has no matching tool_result.

Pipeline: fixToolPairs → fixToolAdjacency → stripTrailingAssistantOrphanToolUse

Closes #2382

Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
2026-05-18 19:18:49 -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
oyi77
32b3549425 test(content): add unit tests for 5 new content providers
16 tests covering:
- Provider registrations (haiper, leonardo, ideogram, suno, udio)
- VIDEO_PROVIDER_IDS includes haiper, leonardo
- Video registry entries (haiper-video, leonardo-video)
- Image registry entries (haiper-image, leonardo-image, ideogram-image)
- Music registry entries (suno-music, udio-music)
- Handler function exports exist

Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
2026-05-18 23:49:51 +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
diegosouzapw
06b34cc12c fix(providers): register llm7 + route Cohere via compatibility layer (#2361 #2360)
Two related provider-registry fixes:

#2361: LLM7.io was visible in the dashboard provider catalog (entry in
src/shared/constants/providers.ts) but the executor registry in
open-sse/config/providerRegistry.ts had no llm7 entry. Every connection
test fell through with a credential error because there was no baseUrl
or authType configured. Added the standard OpenAI-compatible v1
endpoint with a small seed model catalogue.

#2360: Cohere was pointed at the native /v2/chat upstream which returns
the proprietary { message: { content: [{type:'text', text:...}] } }
shape. The combo test validator (extractComboTestResponseText) only
reads the OpenAI choices[] envelope, so a successful Cohere call
surfaced as 'Provider returned HTTP 200 but no text content.' Cohere
publishes an OpenAI-compatible compatibility layer at
/compatibility/v1 that returns { choices: [{ message: { content }}] }
so we route there instead of needing a Cohere-specific translator.

Regression test asserts both registry entries match the expected
shape so a future refactor that reverts the URLs fails loudly.
2026-05-18 10:55:33 -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
9c5b75e3bf test(codex): regression guard for effort suffix priority (#2331)
Adds a deterministic test for the rawEffort resolution chain in
open-sse/executors/codex.ts so a future refactor that flips the priority
back (the bug we just fixed) trips an explicit assertion. Also verifies
end-to-end behavior of the priority order: modelEffort > explicitReasoning
> requestReasoningEffort > fallbackReasoningEffort.

The source fix itself landed earlier on this branch; this commit only
adds the test coverage.
2026-05-18 09:12:36 -03:00
diegosouzapw
d62b128144 fix(account-fallback): classify Anthropic 'Usage Limit Reached' as quota (#2321)
Claude Code Pro/Team users hit a 429 cascade where every Claude account
got marked rate-limited for only a few seconds, retried, hit 429 again,
and the cycle exhausted all accounts until the 5h subscription window
finally reset. Root cause: Anthropic OAuth 429 bodies carry phrases like
'Usage Limit Reached' or 'Claude Pro usage limit reached' that don't
contain the word 'quota'. classifyErrorText fell through to
RATE_LIMIT_EXCEEDED with the OAuth ~5s base cooldown.

Three targeted changes in open-sse/services/accountFallback.ts:

1. classifyErrorText: recognize the Anthropic OAuth phrases ('usage limit
   reached', 'claude pro usage limit', 'you've reached your usage limit'
   and variants) and return QUOTA_EXHAUSTED.

2. checkFallbackError: new branch that, when the classifier flags
   QUOTA_EXHAUSTED for a non-credits/non-daily case, applies a deliberate
   1h cooldown (subscription quotas need real time to reset; the existing
   COOLDOWN_MS.paymentRequired is only 2 minutes).

3. parseRetryFromErrorText: parse absolute ISO 8601 timestamps in the
   body ('Try again at 2026-05-17T10:00:00Z', 'Please wait until ...').
   When present, honor the upstream's stated recovery time instead of
   the 1h default.

Cross-provider fallback for direct (non-combo) calls remains a separate
feature — operators wanting that should route Claude through a combo
that includes openrouter/claude or another fallback target.
2026-05-18 09:11:32 -03:00
diegosouzapw
fe3ab7a836 fix(combo/validator): accept reasoning_content as valid output (#2341)
`validateResponseQuality` flagged any response with `content: null` as
empty and triggered a false-positive 502 combo fallback, even when the
upstream returned the entire answer in `reasoning_content`. This
affected every reasoning model that omits `content` by design:
moonshotai/Kimi-K2.5-TEE, zai-org/GLM-5-TEE, zai-org/GLM-4.7-TEE,
DeepSeek-R1 and Qwen-thinking variants via Chutes.ai, Nvidia, and other
OpenAI-compatible gateways.

Treat a non-empty `reasoning_content` (or its legacy `reasoning` alias)
as valid content. Empty/whitespace-only strings still fall through to
the existing empty-content rejection so we don't weaken the guard.

Backward compat verified: regular content-only and tool_calls-only
responses still validate without change.
2026-05-18 09:10:35 -03:00
diegosouzapw
2af324f6ee fix(docker): ship Dashboard Docs markdown in the container image (#2348)
`.dockerignore` excluded everything under `docs/` except `openapi.yaml`,
which broke the in-product Docs viewer at `/docs/*` with "ENOENT: no
such file or directory, open '/app/docs/SETUP_GUIDE.md'" for every help
page. The previous block-list was meant to keep the image small but it
hid the ~5 MB English markdown tree that the viewer actually reads.

Replace the block with a targeted exclude of the heavy assets that
account for ~45 MB of the original ~50 MB:
- docs/i18n/** (translated copies — viewer falls back to English)
- docs/screenshots/**
- docs/diagrams/exported/** plus raster/SVG variants

Note: Go filepath.Match (Docker's matcher) treats `*` as not crossing
`/`, so the existing `*.md` rule still excludes only root-level
markdown — nested `docs/**/*.md` is implicitly kept without a
re-include rule that would have brought i18n back.

Added a regression test that parses .dockerignore and asserts the
critical English docs survive the filter while the heavy paths still
do not.
2026-05-18 09:09:45 -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
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
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
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
Raxxoor
f6364427ce fix: emit stream errors in client protocol (#2355)
Integrated into release/v3.8.0
2026-05-18 00:24: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
Tyler Ford
d6d585e200 fix: extract flat cached_tokens/reasoning_tokens from OpenAI-compatible usage objects (#2350)
Integrated into release/v3.8.0 — flat cached_tokens/reasoning_tokens extraction for Xiaomi MiMo and similar providers
2026-05-17 21:11:06 -03:00
terence71-glitch
db8a2dc735 fix: preserve Claude Code messages on direct routes (#2351)
Integrated into release/v3.8.0 — Claude Code semantic passthrough for /v1/messages payloads
2026-05-17 21:09:02 -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
terence71-glitch
0e2d04526c fix: honor Codex reasoning suffix aliases (#2335)
Integrated into release/v3.8.0 — fixes issue #2331: Codex model suffix aliases now correctly override client-injected reasoning defaults
2026-05-17 19:38:25 -03:00
thepigdestroyer
696e16b361 fix(claude): cap-aware thinking budget fit for max_tokens (#2327)
Integrated into release/v3.8.0 — fixes HTTP 400 max_tokens > 128000 from Anthropic when using high-effort thinking with Opus 4.7
2026-05-17 19:37:40 -03:00
thepigdestroyer
0f15797e01 fix(v1/messages): default to non-stream for Claude format when ambiguous (#2326)
Integrated into release/v3.8.0 — fixes STREAM_EARLY_EOF on POST /v1/messages when stream is omitted
2026-05-17 19:36:57 -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
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
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
317d146302 Merge release/v3.8.0 into refactor/pages
Resolves conflicts in 9 files to bring 181 commits from release/v3.8.0 into
the dashboard refactor branch ahead of merging back to release.

Layout strategy: our pages overhaul (tabs→pages, restructured sidebar,
removed redundant headers, OpenCode Free no-auth card) is the source of
truth. Release's functional additions are adapted into our layout.

Conflict resolution:

- package.json/package-lock.json: take release's deps (axios bump, CLI v4
  deps, tls-client-node/wreq-js move to optionalDependencies); re-add our
  @xyflow/react addition; regenerate lockfile.
- src/shared/constants/sidebarVisibility.ts: keep our 9-section restructure
  — release's new IDs (limits, media, cli-tools, agents, cloud-agents,
  memory, skills, agent-skills, context-*) are all already present in our
  groups.
- src/i18n/messages/en.json: auto-merge picked up all release's new keys
  (autoCatalog*, quotaCutoffs*, systemTransforms*, schema-coercion, vision);
  only naming conflict was OmniSkills/AgentSkills — kept ours (no space).
- src/app/(dashboard)/dashboard/HomePageClient.tsx: kept our Provider
  Topology card; ported release's TierCoverageWidget (placed before
  topology).
- src/app/(dashboard)/dashboard/settings/page.tsx: kept our redirect to
  /settings/general (we moved tabs to separate pages); release's sticky
  tab CSS change is moot in our structure.
- src/app/(dashboard)/dashboard/skills/page.tsx: rerere applied — release
  hardcoded "OmniSkills" h1 was already removed by our header-cleanup
  refactor.
- src/app/(dashboard)/dashboard/agent-skills/page.tsx: both branches
  created this file independently with identical data source; kept our
  Tailwind-themed 2-column grid (release's version used inline styles).
- src/app/(dashboard)/dashboard/batch/page.tsx: kept our single-tab
  structure (FilesListTab moved to /batch/files page); ported release's
  onRefresh prop addition.
- src/app/(dashboard)/dashboard/batch/files/page.tsx (not in conflict but
  updated): added batches fetch + batches prop to preserve release's
  feature of showing related batches in the file detail modal.

Pre-existing typecheck errors in open-sse/services/contextManager.ts
(lines 141, 154, 167) come from release/v3.8.0 and are not introduced by
this merge.
2026-05-17 01:14:21 -03:00
backryun
cf3262aee8 alibaba provider consolidation (#2319)
Integrated into release/v3.8.0 — consolidates Alibaba-related providers, updates model registries and docs.
2026-05-16 21:50:59 -03:00
Raxxoor
5a7df8ac29 fix: harden stream readiness and build output (#2317)
Integrated into release/v3.8.0 — fixes stream readiness detection for OpenAI Responses API lifecycle events, GLM timeout, Provider Limits UI, and build output cleanup.
2026-05-16 21:47:40 -03:00
oyi77
b83d1a0fc8 feat(provider): add Gitlawb Opengateway provider (xiaomi-mimo + gmi-cloud)
Add two OpenAI-compatible API-key providers via the Gitlawb Opengateway
gateway at opengateway.gitlawb.com:

- gitlawb (alias glb): xiaomi-mimo endpoint with 5 MiMo models
- gitlawb-gmi (alias glb-gmi): gmi-cloud endpoint with 40+ models
  including GPT-5.x, Claude 4.x, DeepSeek, Gemini, Qwen, GLM, Kimi

Both providers include CLI-mimicking headers (User-Agent, X-Title,
HTTP-Referer) to avoid upstream rate limiting. GMI Cloud provider
has passthroughModels enabled since model access varies per API key.
2026-05-17 05:24:11 +07:00
diegosouzapw
08f03a0fa6 fix(auth): stop retrying unrecoverable token refresh failures
Propagate invalid refresh-token errors instead of collapsing them to null
so callers can distinguish expired credentials from transient failures.

Mark affected connections as expired and inactive when refresh fails
with an unrecoverable error, and persist that state during credential
updates. Add tests covering retry bail-out and Claude/Codex refresh
error handling.
2026-05-16 19:12:58 -03:00