fix(guardrails/chat): stop Vision Bridge hijacking credentialed models to opencode-zen - #7204
Conversation
…the default branch (diegosouzapw#7168)
… (queue_conditions alone are eligibility-only) (diegosouzapw#7179)
…s to opencode-zen OpenCode (and similar clients) often send image parts in long sessions. Vision Bridge treated the request model as non-vision and whole-request- rerouted to getBestVisionModel(), which preferred opencode-* (priority 0). That landed on a noauth connection and returned 401 Missing API key — while proxies/combos still logged the original target (zai/glm-5.2, grok-cli, …). Also: after resolveRoutingModel(X-Route-Model), keep body.model aligned so the post-guardrail "body.model !== modelStr" path cannot undo the routing header. - visionBridge: skip whole-request reroute when original model has usable creds - visionBridge: refuse reroute to targets known unusable (noauth without key) - visionBridgeRouter: deprioritize opencode-* for auto vision pick - chat: alignBodyModelWithRouting + only adopt true guardrail model mutations - tests: VB-CRED-01/02 + alignBodyModelWithRouting coverage
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
…04-local # Conflicts: # .mergify.yml
…date stale vision-bridge tests for the credential-aware reroute skip - Extract the routing-model reconciliation logic (X-Route-Model align, post-guardrail reroute policy re-check, hook model override) into RoutingModelOps helpers in resolveRoutingModel.ts, shrinking chat.ts back under the frozen 1796-line file-size baseline (was 1837). - Update tests/unit/guardrails/vision-bridge-callmodel.test.ts: the fallback mock must match whichever API shape the selected fallback model actually calls (OpenAI-compatible vs Anthropic), since the vision-bridge router priority fix in this PR can now legitimately select an Anthropic fallback model instead of always defaulting to an OpenAI-shaped opencode-* model. - Update tests/unit/vision-bridge-policy-reroute-6640.test.ts: per this PR's own VB-CRED-01 test, a credentialed original model is now intentionally never whole-request-rerouted (it always falls through to describe-then- forward) — so the pre-existing diegosouzapw#6640 tests are updated to assert the final, user-facing answer always comes from the original credentialed model, matching the new intended behavior instead of the retired whole-request-reroute path. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
…mplexity to satisfy the project-wide complexity ratchet The new isProviderConnectionUsable helper (complexity 21) regressed the project-wide complexity ratchet from 2056 to 2057. Refactor it to use Set membership checks and small extracted helpers (hasNonEmptyString, hasOAuthCredential) instead of chained === / || comparisons — same behavior, verified by the existing "isProviderConnectionUsable rejects noauth without api key" test, with complexity back under the 15-per-function threshold and the project-wide ratchet back at the 2056 baseline. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
Babysit summaryConflict resolved (DIRTY → CLEAN): merged Fixes, in order:
Verification: targeted test files ( Gate: all green — Unit Tests (1-4/4), Vitest, Fast Quality Gates, No new ESLint warnings, Docs Gates, dast-smoke, semgrep ×2, Change Classification, Merge integrity. Ready for human review & merge. |
|
Hold for a policy decision — do not merge as-is. Tracked in #7303. CI is green here, but auditing the test changes surfaced something bigger than this PR: it inverts two of #6640's three guards while keeping the That is not a defect in the work — the inversion is this PR's actual intent, and it was documented honestly in the file header. The problem is that it's a policy change, and it deserved an explicit decision rather than arriving as a side effect of a CI fix. @diegosouzapw reviewed it and decided (see #7303): Vision Bridge stops whole-request-rerouting entirely — describe-then-forward becomes the single policy, reverting #6640's core mechanism rather than special-casing credentialed models. What that means for this PR:
Worth stating plainly: the 401 in the report is fixed by changes 1+2, but the hijack — a user's configured Thanks for the careful work here — the |
|
Merged into |
…s to opencode-zen (diegosouzapw#7204) * chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (diegosouzapw#7168) * fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (diegosouzapw#7179) * fix(guardrails/chat): stop Vision Bridge hijacking credentialed models to opencode-zen OpenCode (and similar clients) often send image parts in long sessions. Vision Bridge treated the request model as non-vision and whole-request- rerouted to getBestVisionModel(), which preferred opencode-* (priority 0). That landed on a noauth connection and returned 401 Missing API key — while proxies/combos still logged the original target (zai/glm-5.2, grok-cli, …). Also: after resolveRoutingModel(X-Route-Model), keep body.model aligned so the post-guardrail "body.model !== modelStr" path cannot undo the routing header. - visionBridge: skip whole-request reroute when original model has usable creds - visionBridge: refuse reroute to targets known unusable (noauth without key) - visionBridgeRouter: deprioritize opencode-* for auto vision pick - chat: alignBodyModelWithRouting + only adopt true guardrail model mutations - tests: VB-CRED-01/02 + alignBodyModelWithRouting coverage * fix(guardrails/chat): keep chat.ts under the file-size ratchet and update stale vision-bridge tests for the credential-aware reroute skip - Extract the routing-model reconciliation logic (X-Route-Model align, post-guardrail reroute policy re-check, hook model override) into RoutingModelOps helpers in resolveRoutingModel.ts, shrinking chat.ts back under the frozen 1796-line file-size baseline (was 1837). - Update tests/unit/guardrails/vision-bridge-callmodel.test.ts: the fallback mock must match whichever API shape the selected fallback model actually calls (OpenAI-compatible vs Anthropic), since the vision-bridge router priority fix in this PR can now legitimately select an Anthropic fallback model instead of always defaulting to an OpenAI-shaped opencode-* model. - Update tests/unit/vision-bridge-policy-reroute-6640.test.ts: per this PR's own VB-CRED-01 test, a credentialed original model is now intentionally never whole-request-rerouted (it always falls through to describe-then- forward) — so the pre-existing diegosouzapw#6640 tests are updated to assert the final, user-facing answer always comes from the original credentialed model, matching the new intended behavior instead of the retired whole-request-reroute path. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(guardrails/chat): reduce isProviderConnectionUsable cyclomatic complexity to satisfy the project-wide complexity ratchet The new isProviderConnectionUsable helper (complexity 21) regressed the project-wide complexity ratchet from 2056 to 2057. Refactor it to use Set membership checks and small extracted helpers (hasNonEmptyString, hasOAuthCredential) instead of chained === / || comparisons — same behavior, verified by the existing "isProviderConnectionUsable rejects noauth without api key" test, with complexity back under the 15-per-function threshold and the project-wide ratchet back at the 2056 baseline. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
…s to opencode-zen (diegosouzapw#7204) * chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (diegosouzapw#7168) * fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (diegosouzapw#7179) * fix(guardrails/chat): stop Vision Bridge hijacking credentialed models to opencode-zen OpenCode (and similar clients) often send image parts in long sessions. Vision Bridge treated the request model as non-vision and whole-request- rerouted to getBestVisionModel(), which preferred opencode-* (priority 0). That landed on a noauth connection and returned 401 Missing API key — while proxies/combos still logged the original target (zai/glm-5.2, grok-cli, …). Also: after resolveRoutingModel(X-Route-Model), keep body.model aligned so the post-guardrail "body.model !== modelStr" path cannot undo the routing header. - visionBridge: skip whole-request reroute when original model has usable creds - visionBridge: refuse reroute to targets known unusable (noauth without key) - visionBridgeRouter: deprioritize opencode-* for auto vision pick - chat: alignBodyModelWithRouting + only adopt true guardrail model mutations - tests: VB-CRED-01/02 + alignBodyModelWithRouting coverage * fix(guardrails/chat): keep chat.ts under the file-size ratchet and update stale vision-bridge tests for the credential-aware reroute skip - Extract the routing-model reconciliation logic (X-Route-Model align, post-guardrail reroute policy re-check, hook model override) into RoutingModelOps helpers in resolveRoutingModel.ts, shrinking chat.ts back under the frozen 1796-line file-size baseline (was 1837). - Update tests/unit/guardrails/vision-bridge-callmodel.test.ts: the fallback mock must match whichever API shape the selected fallback model actually calls (OpenAI-compatible vs Anthropic), since the vision-bridge router priority fix in this PR can now legitimately select an Anthropic fallback model instead of always defaulting to an OpenAI-shaped opencode-* model. - Update tests/unit/vision-bridge-policy-reroute-6640.test.ts: per this PR's own VB-CRED-01 test, a credentialed original model is now intentionally never whole-request-rerouted (it always falls through to describe-then- forward) — so the pre-existing diegosouzapw#6640 tests are updated to assert the final, user-facing answer always comes from the original credentialed model, matching the new intended behavior instead of the retired whole-request-reroute path. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(guardrails/chat): reduce isProviderConnectionUsable cyclomatic complexity to satisfy the project-wide complexity ratchet The new isProviderConnectionUsable helper (complexity 21) regressed the project-wide complexity ratchet from 2056 to 2057. Refactor it to use Set membership checks and small extracted helpers (hasNonEmptyString, hasOAuthCredential) instead of chained === / || comparisons — same behavior, verified by the existing "isProviderConnectionUsable rejects noauth without api key" test, with complexity back under the 15-per-function threshold and the project-wide ratchet back at the 2056 baseline. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Summary
zai/glm-5.2,grok-cli/*). Previously, image-bearing OpenCode/coding-agent sessions were auto-rerouted togetBestVisionModel(), which preferredopencode-*(priority 0) and landed on a noauth connection → 401 Missing API key, while proxies still logged the original target.noauthwithout API key).opencode-*for auto vision selection.resolveRoutingModel, alignbody.modelwith the routing model so the post-guardrailbody.model !== modelStrpath cannot silently undo the header (header=zai + body=opencode-zen → 401).Repro (before)
zai/glm-5.2(orgrok-cli/...).HTTP … | zai/glm-5.2 | N msgsGuardrail model reroute: zai/glm-5.2 → opencode-zen/gpt-5.4Using opencode-zen account: noauth…→ 401 Missing API keyAfter
zai/glm-5.2/ combovibe/think→ 200 routed tozai; image +grok-cli/grok-build→ routed togrok-cli(not opencode).Test plan
node --import tsx --test tests/unit/guardrails/visionBridge.test.ts tests/unit/resolve-routing-model.test.ts→ 32/32alignBodyModelWithRoutingcoverageFiles
src/lib/guardrails/visionBridge.tssrc/lib/guardrails/visionBridgeRouter.tssrc/sse/handlers/chat.tssrc/sse/handlers/resolveRoutingModel.tstests/unit/guardrails/visionBridge.test.tstests/unit/resolve-routing-model.test.tschangelog.d/fixes/vision-bridge-no-credentialed-hijack.md