Skip to content

Support wildcard/regex model redirects and honor them in provider selection#1

Open
h7ml wants to merge 5 commits intomainfrom
codex/add-model-redirection-support-for-regex
Open

Support wildcard/regex model redirects and honor them in provider selection#1
h7ml wants to merge 5 commits intomainfrom
codex/add-model-redirection-support-for-regex

Conversation

@h7ml
Copy link
Copy Markdown
Member

@h7ml h7ml commented Feb 7, 2026

Motivation

  • Allow providers to declare flexible model redirection rules (exact, wildcard like gpt-*, or regex /^...$/) so incoming requests can be mapped to an upstream model even when the requested name is not an exact whitelist entry.
  • Make redirect decisions auditable by recording matched pattern metadata into the provider decision chain so routing/debugging can surface why a model was rewritten.

Description

  • Implemented pattern matching in src/app/v1/_lib/proxy/model-redirector.ts with parseRegexPattern, buildWildcardRegex, and findRedirectMatch to support exact, wildcard, and regex matches, and wired them into ModelRedirector.apply, ModelRedirector.getRedirectedModel, and ModelRedirector.hasRedirect.
  • Record matched pattern and match type into the provider decision entry by extending ProviderChainItem.modelRedirect in src/types/message.ts with matchedPattern and matchType fields.
  • Updated provider selection logic in src/app/v1/_lib/proxy/provider-selector.ts to consider redirect patterns when evaluating allowedModels so Claude providers with matching redirect patterns can be selected even if allowedModels excludes the requested model.
  • Added unit tests covering pattern matching and selection behavior in tests/unit/proxy/model-redirector-patterns.test.ts and extended tests/unit/proxy/provider-selector-model-redirect.test.ts to include wildcard/redirect scenarios.

Testing

  • Added unit tests under tests/unit/proxy/ but running the project automation failed in this environment because runtime tools are not installed: bun run build failed (next not found), bun run lint and bun run lint:fix failed (biome not found), bun run typecheck failed (tsgo not found), and bun run test failed (vitest not found).
  • No automated tests were executed successfully in this environment due to the missing tooling, however new test files were added and committed for CI to run in a properly provisioned environment (tests/unit/proxy/model-redirector-patterns.test.ts and updated tests/unit/proxy/provider-selector-model-redirect.test.ts).

Codex Task

Summary by CodeRabbit

版本发布说明

  • 新功能

    • 增强模型重定向匹配功能,现已支持精确匹配、通配符模式和正则表达式三种匹配方式。
    • 改进重定向日志记录,提供更详细的匹配模式和匹配类型信息。
  • 测试

    • 新增单元测试,验证通配符和正则表达式模式匹配的正确性。
    • 新增提供商选择器测试,确保跨提供商模型重定向的可靠性。

h7ml added 5 commits January 29, 2026 23:37
Signed-off-by: h7ml <h7ml@qq.com>
Signed-off-by: h7ml <h7ml@qq.com>
Signed-off-by: h7ml <h7ml@qq.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 7, 2026

概览

此次变更为模型重定向功能引入了多模式匹配系统,支持精确匹配、通配符和正则表达式三种模式类型。新增工具函数用于解析和验证重定向模式,并通过统一的查找逻辑替换了直接映射查询,同时将匹配细节传播到提供者链中。

变更

变更集群 / 文件 摘要
模型重定向核心实现
src/app/v1/_lib/proxy/model-redirector.ts
新增 RedirectMatchType 和 ModelRedirectMatch 类型;引入 parseRegexPattern、escapeRegex、buildWildcardRegex 等工具函数;实现 findRedirectMatch 函数,统一处理精确、通配符和正则三种重定向模式的匹配逻辑。
提供者选择器集成
src/app/v1/_lib/proxy/provider-selector.ts
更新模型支持检查逻辑,集成 ModelRedirector.hasRedirect,使跨类型委托和重定向检查能够协同工作。
消息类型扩展
src/types/message.ts
扩展 ProviderChainItem 接口,新增可选字段 matchedPattern 和 matchType,用于记录重定向匹配细节。
测试覆盖
tests/unit/proxy/model-redirector-patterns.test.ts, tests/unit/proxy/provider-selector-model-redirect.test.ts
新增单元测试覆盖通配符模式、正则表达式模式的匹配验证,以及提供者选择与重定向的集成场景。

序列图

sequenceDiagram
    participant Client as 客户端请求
    participant ProviderSelector as 提供者选择器
    participant ModelRedirector as 模型重定向器
    participant ProviderChain as 提供者链
    
    Client->>ProviderSelector: 请求模型支持检查
    ProviderSelector->>ModelRedirector: hasRedirect(modelRedirects, model)
    ModelRedirector->>ModelRedirector: 逐一尝试精确/通配符/正则匹配
    ModelRedirector-->>ProviderSelector: 返回匹配结果(true/false)
    
    alt 找到重定向匹配
        ProviderSelector->>ModelRedirector: findRedirectMatch(...)
        ModelRedirector-->>ProviderSelector: 返回 {redirectedModel, matchedPattern, matchType}
        ProviderSelector->>ProviderChain: 存储匹配细节到 ProviderChainItem
    else 无匹配
        ProviderSelector->>ProviderChain: 使用原始模型
    end
    
    ProviderChain-->>Client: 返回重定向结果
Loading

预估代码审查工作量

🎯 3 (中等) | ⏱️ ~25 分钟

诗歌

🐰 ✨ 模式在林间闪烁,
精确、通配、正则共舞,
重定向之光穿透层层网络,
模型各得其所,流转如意,
兔子们为此舞蹈欢呼!🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding support for wildcard/regex model redirects and integrating them into provider selection logic across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-model-redirection-support-for-regex

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/app/v1/_lib/proxy/model-redirector.ts`:
- Around line 13-31: The parseRegexPattern function currently accepts
arbitrarily long regex bodies which can lead to catastrophic backtracking
(ReDoS) when later used (e.g., regexPattern.test(originalModel)); add a
defensively small maximum length check for the extracted body inside
parseRegexPattern (and reject patterns where body.length exceeds the limit) so
that dangerous or overly complex patterns are not compiled; keep the existing
try/catch for syntax errors and return null on length violation to preserve
current behavior.
🧹 Nitpick comments (2)
tests/unit/proxy/provider-selector-model-redirect.test.ts (1)

51-125: 测试覆盖建议:补充反向测试用例。

当前两个新测试只验证了通配符匹配时供应商被选中的场景。建议补充以下用例以增强覆盖:

  1. 不匹配时应被过滤:请求的模型不匹配任何 redirect 模式时,provider 应为 null
  2. 正则模式:除通配符外,测试 /^.../$/ 格式的 regex redirect 在 provider selection 中的行为。
src/app/v1/_lib/proxy/model-redirector.ts (1)

46-76: findRedirectMatch 中 RegExp 对象未缓存,每次调用重复编译。

hasRedirect 在供应商选择循环中为每个 provider 调用,每次都会对所有 pattern 重新执行 parseRegexPattern / buildWildcardRegex(构造 new RegExp)。在典型规模下影响不大,但如果 provider 或 pattern 数量增长,性能可能成为瓶颈。

可考虑在 provider 级别缓存已编译的正则(如使用 WeakMap<Provider, Map<string, RegExp>>),或在 findRedirectMatch 外层做一次预编译。

Comment on lines +13 to +31
function parseRegexPattern(pattern: string): RegExp | null {
if (!pattern.startsWith("/")) {
return null;
}
const lastSlashIndex = pattern.lastIndexOf("/");
if (lastSlashIndex <= 0) {
return null;
}
const body = pattern.slice(1, lastSlashIndex);
const flags = pattern.slice(lastSlashIndex + 1);
if (!body) {
return null;
}
try {
return new RegExp(body, flags);
} catch {
return null;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

JavaScript ReDoS protection libraries 2025

💡 Result:

JavaScript ReDoS protection libraries (practical options in/around 2025)

1) Switch to a non-backtracking regex engine (strongest runtime protection)

  • re2 (node-re2): Native Node.js bindings to Google’s RE2; avoids catastrophic backtracking but does not support some JS regex features (notably lookarounds and backreferences). [1]
  • re2-wasm: Runs RE2 via WebAssembly (useful when native addons are undesirable); same “safe engine” idea and similar feature tradeoffs. [2][3]

2) Detect risky regexes (CI/lint/test-time controls)

  • redos-detector: Library/CLI that scores how vulnerable a pattern is (browser/Node/Deno). [4]
  • safe-regex2: Maintained fork of safe-regex; fast heuristic (“star height”) check—expects false positives/negatives (good as a cheap first pass). [5]
  • safe-regex: Original package (widely used but older; same heuristic limitations). [6]
  • vuln-regex-detector / davisjam/vuln-regex-detector: Heavier-weight detection aimed at finding real catastrophic-backtracking cases (includes project scanning workflow; testing can be expensive). [7][8]

3) Operational mitigations you should still do

Even with tooling, also apply input length limits and avoid building regexes directly from untrusted input. [9]

Notes on RE2 compatibility: if you plan to move patterns to RE2, many constructs are intentionally avoided/unsupported (e.g., lookarounds, backreferences). [1][10]

Sources:
[1] re2 (node-re2) package info on RE2 safety + unsupported features [2] re2-wasm npm [3] google/re2-wasm repo [4] redos-detector npm [5] safe-regex2 npm [6] safe-regex npm [7] vuln-regex-detector npm [8] davisjam/vuln-regex-detector GitHub [9] Bearer rule guidance on ReDoS mitigations [10] OWASP CRS note on non-backtracking/RE2-compatible regex constraints


🌐 Web query:

regular expression denial of service prevention techniques Node.js

💡 Result:

ReDoS prevention techniques in Node.js (practical checklist)

  1. Avoid “evil” patterns in the first place

    • Biggest risks: nested quantifiers and overlapping alternation (classic catastrophic backtracking). OWASP explicitly warns to “stay away from evil regexes” in Node.js. [1]
  2. Constrain what the regex has to process

    • Hard cap input length (and ideally also min/max lengths in the regex like {1,25}) as part of input validation guidance. [2]
    • Prefer allowlists (tight character classes) over broad patterns like .* / .+ when possible.
  3. Use a linear-time regex engine for untrusted regexes (or high-risk matches)

    • If users can supply patterns (or you must run complex patterns on untrusted data), use RE2 via the re2 (node-re2) package; it avoids catastrophic backtracking by using a non-backtracking engine (with feature tradeoffs like no lookaheads/backreferences). [3]
  4. Detect risky regexes in CI / code review

    • Add automated checks like safe-regex2 to flag potentially catastrophic patterns (useful, but it can have false positives/negatives). [4]
    • OWASP notes there are tools to test whether regexes are vulnerable to ReDoS. [1]
  5. Put regex work behind a “circuit breaker”

    • Node doesn’t have a built-in per-regex timeout, so for worst-case safety run high-risk matches in a Worker thread / child process and enforce a deadline (terminate the worker/process if it exceeds).
    • If you control Node/V8 startup flags, V8 has an experimental non-backtracking fallback after “excessive backtracks” (runtime hardening option; pattern/flag limitations apply). [5]

Sources: OWASP Node.js Security Cheat Sheet [1], OWASP Input Validation Cheat Sheet [2], re2/node-re2 docs [3], safe-regex2 docs [4], V8 non-backtracking RegExp engine blog [5].


🏁 Script executed:

# First, let's examine the actual file to understand the context better
cat -n src/app/v1/_lib/proxy/model-redirector.ts | head -100

Repository: dext7r/claude-code-hub

Length of output: 3456


🏁 Script executed:

# Let's check how modelRedirects is defined and where parseRegexPattern is used
rg "modelRedirects" src/app/v1/_lib/proxy/model-redirector.ts -A 2 -B 2

Repository: dext7r/claude-code-hub

Length of output: 1478


🏁 Script executed:

# Check the provider configuration to understand the source of modelRedirects data
rg "modelRedirects" src/ -l | head -20

Repository: dext7r/claude-code-hub

Length of output: 764


🏁 Script executed:

# Let's check how the modelRedirects are populated to confirm they're admin-configured
rg "provider\.modelRedirects\s*=" src/ -A 3 | head -40

Repository: dext7r/claude-code-hub

Length of output: 48


🏁 Script executed:

# Check the types to understand the Provider structure and source of modelRedirects
cat -n src/types/provider.ts | grep -A 5 -B 5 "modelRedirects"

Repository: dext7r/claude-code-hub

Length of output: 833


ReDoS 风险:管理员配置的正则模式可导致事件循环阻塞。

虽然 modelRedirects 来自供应商配置而非终端用户 HTTP 请求(降低了攻击面),但恶意或失误的正则表达式(如 /(a+)+$/)仍可在 regexPattern.test(originalModel) 执行时触发灾难性回溯,阻塞事件循环。现有的 try-catch 仅捕获语法错误,无法阻止计算复杂度攻击。

建议在 parseRegexPattern 中添加长度限制,这是业界公认的防护措施:

修复方案:添加正则 body 长度上限
 function parseRegexPattern(pattern: string): RegExp | null {
   if (!pattern.startsWith("/")) {
     return null;
   }
   const lastSlashIndex = pattern.lastIndexOf("/");
   if (lastSlashIndex <= 0) {
     return null;
   }
   const body = pattern.slice(1, lastSlashIndex);
   const flags = pattern.slice(lastSlashIndex + 1);
-  if (!body) {
+  if (!body || body.length > 200) {
     return null;
   }
   try {
     return new RegExp(body, flags);
   } catch {
     return null;
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function parseRegexPattern(pattern: string): RegExp | null {
if (!pattern.startsWith("/")) {
return null;
}
const lastSlashIndex = pattern.lastIndexOf("/");
if (lastSlashIndex <= 0) {
return null;
}
const body = pattern.slice(1, lastSlashIndex);
const flags = pattern.slice(lastSlashIndex + 1);
if (!body) {
return null;
}
try {
return new RegExp(body, flags);
} catch {
return null;
}
}
function parseRegexPattern(pattern: string): RegExp | null {
if (!pattern.startsWith("/")) {
return null;
}
const lastSlashIndex = pattern.lastIndexOf("/");
if (lastSlashIndex <= 0) {
return null;
}
const body = pattern.slice(1, lastSlashIndex);
const flags = pattern.slice(lastSlashIndex + 1);
if (!body || body.length > 200) {
return null;
}
try {
return new RegExp(body, flags);
} catch {
return null;
}
}
🧰 Tools
🪛 ast-grep (0.40.5)

[warning] 26-26: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(body, flags)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
In `@src/app/v1/_lib/proxy/model-redirector.ts` around lines 13 - 31, The
parseRegexPattern function currently accepts arbitrarily long regex bodies which
can lead to catastrophic backtracking (ReDoS) when later used (e.g.,
regexPattern.test(originalModel)); add a defensively small maximum length check
for the extracted body inside parseRegexPattern (and reject patterns where
body.length exceeds the limit) so that dangerous or overly complex patterns are
not compiled; keep the existing try/catch for syntax errors and return null on
length violation to preserve current behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant