fix(security): preserve stored token through empty-token UI state; handle non-JSON health responses

GatewayClient.ts — autosave effects no longer overwrite persisted gateway tokens with
empty strings. When the in-memory token is empty (proxy handles auth server-side),
the patch omits the token field (undefined) so mergeGatewaySettings/mergeGatewayProfiles
treats it as "leave unchanged". adapterProfiles updater also preserves the existing
stored token when the new token is empty, preventing floor-switch from erasing tokens.

runtime/custom/http.ts — requestCustomRuntime() checks the response Content-Type before
calling response.json(). Non-JSON responses (e.g. plain-text /health "OK") are returned
as-is instead of throwing a JSON parse error, making the custom/local/claw3d health
probe path reliable for runtimes that return plain text.

Authored By: GSKNNFT
This commit is contained in:
gsknnft
2026-04-18 20:47:45 -04:00
parent 5980a65c2b
commit da4f519745
2 changed files with 16 additions and 6 deletions
+11 -6
View File
@@ -1048,11 +1048,12 @@ export const useGatewayConnection = (
useEffect(() => {
if (!settingsLoaded) return;
setAdapterProfiles((current) => {
const nextProfile = {
url: gatewayUrl.trim(),
token,
};
const existing = current[selectedAdapterType];
// Never overwrite a stored token with an empty string — the Studio proxy
// injects the real token server-side; an empty token in UI state means
// "let the proxy handle it", not "clear the saved token".
const nextToken = token || existing?.token || "";
const nextProfile = { url: gatewayUrl.trim(), token: nextToken };
if (
existing &&
existing.url === nextProfile.url &&
@@ -1072,11 +1073,15 @@ export const useGatewayConnection = (
const baseline = loadedGatewaySettings.current;
if (!baseline) return;
const nextGatewayUrl = gatewayUrl.trim();
// Use undefined for the token in the patch when the in-memory token is empty so
// that mergeGatewaySettings / mergeGatewayProfiles treats it as "leave unchanged"
// rather than overwriting the persisted token with an empty string.
const persistToken = token || undefined;
const nextProfiles = {
...adapterProfiles,
[selectedAdapterType]: {
url: nextGatewayUrl,
token,
token: persistToken,
},
};
if (
@@ -1091,7 +1096,7 @@ export const useGatewayConnection = (
{
gateway: {
url: nextGatewayUrl,
token,
token: persistToken,
adapterType: selectedAdapterType,
profiles: nextProfiles,
},
+5
View File
@@ -54,6 +54,11 @@ export async function requestCustomRuntime<T = unknown>({
text.trim() || `Custom runtime request failed (${response.status}) for ${pathname}.`
);
}
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.includes("application/json")) {
// Non-JSON success response (e.g. plain-text /health "OK") — return as-is.
return (await response.text()) as unknown as T;
}
return (await response.json()) as T;
}