mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
A working Qoder PAT was reported as "expired". The validator probes the Cosy endpoint (api1.qoder.sh) — which IS the correct PAT path (the executor falls back to it after the expected 401 from api.qoder.com). The bug was the verdict: isCosyAppError (added by #2860) marked ANY Cosy 500 with "success":false as an auth failure, including a generic {..."msgCode":500,"message":"Internal Server Error"} server fault — contradicting the older #1391 "5xx = valid bypass" rule. Narrow it: a Cosy 500 only marks the PAT invalid when the body carries an EXPLICIT auth signal (unauthorized/forbidden/expired/token invalid/...); a generic Internal Server Error falls back to valid-bypass. #2860's protection for genuine auth rejections is preserved. Regression test: tests/unit/qoder-cli.test.ts — the two pre-existing generic-500 cases now assert valid:true (they encoded the #3247 bug) + a new explicit-auth-signal case asserts valid:false. 13/13 green.
This commit is contained in:
committed by
GitHub
parent
36932b62a7
commit
a7e445edea
@@ -49,6 +49,7 @@ Thanks to everyone whose work landed in v3.8.12:
|
||||
- **sse/groq:** non-reasoning Groq models (`llama-3.3-70b-versatile`, `llama-4-scout`) are now flagged `supportsReasoning: false`, so `reasoning_effort` / `output_config.effort` / `thinking` are stripped before dispatch instead of being forwarded and rejected with HTTP 400 — fixes the Claude Code → Groq regression of #764 ([#3258](https://github.com/diegosouzapw/OmniRoute/issues/3258))
|
||||
- **api/images:** `POST /v1/images/edits` to a custom OpenAI-compatible provider no longer forwards an empty `model`. The multipart body is now built as a `Buffer` with an explicit boundary instead of a global `FormData` — the patched undici `fetch` serialized a native `FormData` as the literal string `[object FormData]` (text/plain), dropping every field including `model` ([#3273](https://github.com/diegosouzapw/OmniRoute/issues/3273))
|
||||
- **api/webhooks:** webhook URLs may now target a private/internal address (e.g. `192.168.x`, a docker-internal host) when `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` — the webhook guard reuses the same explicit opt-in as private provider URLs (default OFF; protocol and embedded-credential checks stay unconditional). Cloud-metadata / link-local endpoints (`169.254.169.254`, `metadata.google.internal`, `100.100.100.200`, `169.254.0.0/16`) are blocked **unconditionally** even with the opt-in on, and the webhook test endpoint redacts the upstream response body for private targets (no SSRF→IAM-credential pivot, no content exfiltration) ([#3269](https://github.com/diegosouzapw/OmniRoute/issues/3269))
|
||||
- **sse/qoder:** a valid Qoder Personal Access Token is no longer wrongly reported as "expired" when the Cosy validation endpoint returns a generic `Internal Server Error` (HTTP 500). A Cosy 500 only marks the PAT invalid when its body carries an explicit auth signal; a generic server fault now falls back to the #1391 valid-bypass rule ([#3247](https://github.com/diegosouzapw/OmniRoute/issues/3247))
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -503,18 +503,24 @@ export async function validateQoderCliPat({
|
||||
return { valid: true, error: null, unsupported: false };
|
||||
}
|
||||
|
||||
// Treat 5xx as valid bypass to prevent false negatives from legacy Qoder APIs (issue #1391)
|
||||
// Treat 5xx as a valid bypass to prevent false negatives from legacy Qoder APIs (#1391).
|
||||
// A Cosy `{"success":false}` 500 is ambiguous: it can be a genuine auth rejection OR a
|
||||
// transient/generic upstream "Internal Server Error". Only mark the PAT invalid when the
|
||||
// body carries an EXPLICIT auth signal — a generic 500 is a server fault, not an auth
|
||||
// verdict, so a working PAT must not be reported as expired (#3247, narrowing #2860).
|
||||
if (res.status >= 500) {
|
||||
const isCosyAppError =
|
||||
/"success"\s*:\s*false/.test(errorDetail) &&
|
||||
(/"msgCode"\s*:\s*500/.test(errorDetail) || /internal\s*server\s*error/i.test(errorDetail));
|
||||
const isCosyResponse = /"success"\s*:\s*false/.test(errorDetail);
|
||||
const hasAuthSignal =
|
||||
/(unauthorized|forbidden|expired|revoked|not\s*authorized|permission\s*denied|access\s*denied|invalid\s*(?:token|credential|api[\s_-]*key)|token\s*(?:invalid|expired|revoked))/i.test(
|
||||
errorDetail
|
||||
);
|
||||
|
||||
if (isCosyAppError) {
|
||||
if (isCosyResponse && hasAuthSignal) {
|
||||
return {
|
||||
valid: false,
|
||||
error:
|
||||
`Authentication failed (HTTP ${res.status}). The Qoder Cosy server returned an Internal Server Error. ` +
|
||||
"This typically indicates that your Personal Access Token is invalid, expired, or not authorized. " +
|
||||
`Authentication failed (HTTP ${res.status}). The Qoder Cosy server rejected the token ` +
|
||||
"as invalid, expired, or not authorized. " +
|
||||
"Please check your token at https://qoder.com/account/integrations." +
|
||||
(errorDetail ? ` Server response: ${errorDetail}` : ""),
|
||||
unsupported: false,
|
||||
|
||||
@@ -365,37 +365,59 @@ test("validateQoderCliPat treats 5xx HTTP failures as valid bypass", async () =>
|
||||
}
|
||||
});
|
||||
|
||||
test("validateQoderCliPat rejects 500 HTTP failures if response is Cosy app-level auth error", async () => {
|
||||
// #3247: a generic Cosy 500 (`{"success":false,...,"msgCode":500,"message":"Internal
|
||||
// Server Error"}`) is a SERVER fault, not a reliable auth verdict — a PAT that works in
|
||||
// the Qoder CLI was being wrongly marked "expired". Per the older #1391 rule, a generic
|
||||
// 5xx is now a valid bypass; only an explicit auth signal in the body marks it invalid.
|
||||
test("validateQoderCliPat treats a generic Cosy 500 (no auth signal) as a valid bypass (#3247)", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url) => {
|
||||
if (String(url).includes("/ping")) return new Response("pong", { status: 200 });
|
||||
return new Response(
|
||||
' { "success" : false, "traceId": "a4e5de61929400b9243b4f6e49756906", "msgCode" : 500 } ',
|
||||
'{"success":false,"traceId":"a4e5de61929400b9243b4f6e49756906","msgCode":500,"msgInfo":"Internal Server Error","message":"Internal Server Error"}',
|
||||
{ status: 500 }
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await qoderCli.validateQoderCliPat({ apiKey: "invalid-pat" });
|
||||
assert.equal(result.valid, false);
|
||||
assert.match(result.error!, /Authentication failed \(HTTP 500\)/);
|
||||
const result = await qoderCli.validateQoderCliPat({ apiKey: "pt-valid-token" });
|
||||
assert.equal(result.valid, true);
|
||||
assert.match(result.error!, /treating PAT as valid/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("validateQoderCliPat rejects 500 HTTP failures using regex for Internal Server Error with whitespace", async () => {
|
||||
test("validateQoderCliPat treats a generic 'Internal Server Error' 500 as a valid bypass (#3247)", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url) => {
|
||||
if (String(url).includes("/ping")) return new Response("pong", { status: 200 });
|
||||
return new Response(' { "success" : false, "error": "Internal Server Error" } ', {
|
||||
status: 500,
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await qoderCli.validateQoderCliPat({ apiKey: "pt-valid-token" });
|
||||
assert.equal(result.valid, true);
|
||||
assert.match(result.error!, /treating PAT as valid/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("validateQoderCliPat still rejects a Cosy 500 that carries an explicit auth signal (#2860)", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url) => {
|
||||
if (String(url).includes("/ping")) return new Response("pong", { status: 200 });
|
||||
return new Response(
|
||||
' { "success" : false, "error": "Internal Server Error" } ',
|
||||
'{"success":false,"msgCode":500,"message":"token invalid or unauthorized"}',
|
||||
{ status: 500 }
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await qoderCli.validateQoderCliPat({ apiKey: "invalid-pat" });
|
||||
const result = await qoderCli.validateQoderCliPat({ apiKey: "pt-bad-token" });
|
||||
assert.equal(result.valid, false);
|
||||
assert.match(result.error!, /Authentication failed \(HTTP 500\)/);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user