mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-04 08:03:41 +00:00
fix(auth): recover from malformed API-key profiles (#97520)
* fix: reject malformed API-key auth profiles * fix(auth): detect onboard command API-key variants * fix(auth): reject malformed API-key flags
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
maybeApplyApiKeyFromOption,
|
||||
normalizeApiKeyInput,
|
||||
normalizeTokenProviderInput,
|
||||
validateApiKeyInput,
|
||||
} from "./provider-auth-input.js";
|
||||
|
||||
const acceptAnyApiKeyInput = () => undefined;
|
||||
@@ -240,6 +241,19 @@ describe("normalizeApiKeyInput", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateApiKeyInput", () => {
|
||||
it.each([
|
||||
"openclaw onboard --auth-choice zai-coding-global",
|
||||
"openclaw onboard --auth-choice=zai-coding-global",
|
||||
"openclaw onboard --non-interactive --auth-choice zai-coding-global --zai-api-key $ZAI_API_KEY",
|
||||
"openclaw onboard --non-interactive --auth-choice=zai-coding-global --zai-api-key $ZAI_API_KEY",
|
||||
])("rejects pasted OpenClaw onboarding command %p", (value) => {
|
||||
expect(validateApiKeyInput(value)).toBe(
|
||||
"Paste the API key value, not an OpenClaw onboarding command.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("maybeApplyApiKeyFromOption", () => {
|
||||
it.each(["demo-provider", " DeMo-PrOvIdEr "])(
|
||||
"stores normalized token when provider %p matches",
|
||||
@@ -265,6 +279,23 @@ describe("maybeApplyApiKeyFromOption", () => {
|
||||
expect(result).toBeUndefined();
|
||||
expect(setCredential).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects malformed command-shaped option keys before storing them", async () => {
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
await expect(
|
||||
maybeApplyApiKeyFromOption({
|
||||
token:
|
||||
"openclaw onboard --non-interactive --auth-choice=zai-coding-global --zai-api-key $ZAI_API_KEY",
|
||||
tokenProvider: "zai",
|
||||
expectedProviders: ["zai"],
|
||||
normalize: normalizeApiKeyInput,
|
||||
validate: validateApiKeyInput,
|
||||
setCredential,
|
||||
}),
|
||||
).rejects.toThrow("Paste the API key value, not an OpenClaw onboarding command.");
|
||||
expect(setCredential).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "@openclaw/normalization-core/string-coerce";
|
||||
import { isMalformedApiKeyInput } from "../agents/auth-profiles/credential-state.js";
|
||||
import { resolveEnvApiKey } from "../agents/model-auth-env.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { SecretInput } from "../config/types.secrets.js";
|
||||
@@ -55,8 +56,16 @@ export function normalizeApiKeyInput(raw: string): string {
|
||||
}
|
||||
|
||||
/** Validates required API-key input for setup prompts. */
|
||||
export const validateApiKeyInput = (value: string) =>
|
||||
normalizeApiKeyInput(value).length > 0 ? undefined : "Required";
|
||||
export const validateApiKeyInput = (value: string) => {
|
||||
const normalized = normalizeApiKeyInput(value);
|
||||
if (!normalized) {
|
||||
return "Required";
|
||||
}
|
||||
if (isMalformedApiKeyInput(normalized)) {
|
||||
return "Paste the API key value, not an OpenClaw onboarding command.";
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** Formats a redacted API-key preview for setup confirmation prompts. */
|
||||
export function formatApiKeyPreview(
|
||||
@@ -105,6 +114,7 @@ export async function maybeApplyApiKeyFromOption(params: {
|
||||
secretInputMode?: SecretInputMode;
|
||||
expectedProviders: string[];
|
||||
normalize: (value: string) => string;
|
||||
validate?: (value: string) => string | undefined;
|
||||
setCredential: (apiKey: SecretInput, mode?: SecretInputMode) => Promise<void>;
|
||||
}): Promise<string | undefined> {
|
||||
const tokenProvider = normalizeTokenProviderInput(params.tokenProvider);
|
||||
@@ -115,6 +125,10 @@ export async function maybeApplyApiKeyFromOption(params: {
|
||||
return undefined;
|
||||
}
|
||||
const apiKey = params.normalize(params.token);
|
||||
const validationError = params.validate?.(apiKey);
|
||||
if (validationError) {
|
||||
throw new Error(validationError);
|
||||
}
|
||||
await params.setCredential(apiKey, params.secretInputMode);
|
||||
return apiKey;
|
||||
}
|
||||
@@ -143,6 +157,7 @@ export async function ensureApiKeyFromOptionEnvOrPrompt(params: {
|
||||
secretInputMode: params.secretInputMode,
|
||||
expectedProviders: params.expectedProviders,
|
||||
normalize: params.normalize,
|
||||
validate: params.validate,
|
||||
setCredential: params.setCredential,
|
||||
});
|
||||
if (optionApiKey) {
|
||||
|
||||
Reference in New Issue
Block a user