* Secrets: add inline allowlist review set * Secrets: narrow detect-secrets file exclusions * Secrets: exclude Docker fingerprint false positive * Secrets: allowlist test and docs false positives * Secrets: refresh baseline after allowlist updates * Secrets: fix gateway chat fixture pragma * Secrets: format pre-commit config * Android: keep talk mode fixture JSON valid * Feishu: rely on client timeout injection * Secrets: allowlist provider auth test fixtures * Secrets: allowlist onboard search fixtures * Secrets: allowlist onboard mode fixture * Secrets: allowlist gateway auth mode fixture * Secrets: allowlist APNS wake test key * Secrets: allowlist gateway reload fixtures * Secrets: allowlist moonshot video fixture * Secrets: allowlist auto audio fixture * Secrets: allowlist tiny audio fixture * Secrets: allowlist embeddings fixtures * Secrets: allowlist resolve fixtures * Secrets: allowlist target registry pattern fixtures * Secrets: allowlist gateway chat env fixture * Secrets: refresh baseline after fixture allowlists * Secrets: reapply gateway chat env allowlist * Secrets: reapply gateway chat env allowlist * Secrets: stabilize gateway chat env allowlist * Secrets: allowlist runtime snapshot save fixture * Secrets: allowlist oauth profile fixtures * Secrets: allowlist compaction identifier fixture * Secrets: allowlist model auth fixture * Secrets: allowlist model status fixtures * Secrets: allowlist custom onboarding fixture * Secrets: allowlist mattermost token summary fixtures * Secrets: allowlist gateway auth suite fixtures * Secrets: allowlist channel summary fixture * Secrets: allowlist provider usage auth fixtures * Secrets: allowlist media proxy fixture * Secrets: allowlist secrets audit fixtures * Secrets: refresh baseline after final fixture allowlists * Feishu: prefer explicit client timeout * Feishu: test direct timeout precedence
129 lines
4.0 KiB
TypeScript
129 lines
4.0 KiB
TypeScript
import {
|
|
ensureAuthProfileStore,
|
|
resolveApiKeyForProfile,
|
|
resolveAuthProfileOrder,
|
|
} from "../../agents/auth-profiles.js";
|
|
import { resolveEnvApiKey } from "../../agents/model-auth.js";
|
|
import type { OpenClawConfig } from "../../config/config.js";
|
|
import type { RuntimeEnv } from "../../runtime.js";
|
|
import { normalizeOptionalSecretInput } from "../../utils/normalize-secret-input.js";
|
|
import type { SecretInputMode } from "../onboard-types.js";
|
|
|
|
export type NonInteractiveApiKeySource = "flag" | "env" | "profile";
|
|
|
|
function parseEnvVarNameFromSourceLabel(source: string | undefined): string | undefined {
|
|
if (!source) {
|
|
return undefined;
|
|
}
|
|
const match = /^(?:shell env: |env: )([A-Z][A-Z0-9_]*)$/.exec(source.trim());
|
|
return match?.[1];
|
|
}
|
|
|
|
async function resolveApiKeyFromProfiles(params: {
|
|
provider: string;
|
|
cfg: OpenClawConfig;
|
|
agentDir?: string;
|
|
}): Promise<string | null> {
|
|
const store = ensureAuthProfileStore(params.agentDir);
|
|
const order = resolveAuthProfileOrder({
|
|
cfg: params.cfg,
|
|
store,
|
|
provider: params.provider,
|
|
});
|
|
for (const profileId of order) {
|
|
const cred = store.profiles[profileId];
|
|
if (cred?.type !== "api_key") {
|
|
continue;
|
|
}
|
|
const resolved = await resolveApiKeyForProfile({
|
|
cfg: params.cfg,
|
|
store,
|
|
profileId,
|
|
agentDir: params.agentDir,
|
|
});
|
|
if (resolved?.apiKey) {
|
|
return resolved.apiKey;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function resolveNonInteractiveApiKey(params: {
|
|
provider: string;
|
|
cfg: OpenClawConfig;
|
|
flagValue?: string;
|
|
flagName: string;
|
|
envVar: string;
|
|
envVarName?: string;
|
|
runtime: RuntimeEnv;
|
|
agentDir?: string;
|
|
allowProfile?: boolean;
|
|
required?: boolean;
|
|
secretInputMode?: SecretInputMode;
|
|
}): Promise<{ key: string; source: NonInteractiveApiKeySource; envVarName?: string } | null> {
|
|
const flagKey = normalizeOptionalSecretInput(params.flagValue);
|
|
const envResolved = resolveEnvApiKey(params.provider);
|
|
const explicitEnvVar = params.envVarName?.trim();
|
|
const explicitEnvKey = explicitEnvVar
|
|
? normalizeOptionalSecretInput(process.env[explicitEnvVar])
|
|
: undefined;
|
|
const resolvedEnvKey = envResolved?.apiKey ?? explicitEnvKey;
|
|
const resolvedEnvVarName = parseEnvVarNameFromSourceLabel(envResolved?.source) ?? explicitEnvVar;
|
|
|
|
const useSecretRefMode = params.secretInputMode === "ref"; // pragma: allowlist secret
|
|
if (useSecretRefMode) {
|
|
if (!resolvedEnvKey && flagKey) {
|
|
params.runtime.error(
|
|
[
|
|
`${params.flagName} cannot be used with --secret-input-mode ref unless ${params.envVar} is set in env.`,
|
|
`Set ${params.envVar} in env and omit ${params.flagName}, or use --secret-input-mode plaintext.`,
|
|
].join("\n"),
|
|
);
|
|
params.runtime.exit(1);
|
|
return null;
|
|
}
|
|
if (resolvedEnvKey) {
|
|
if (!resolvedEnvVarName) {
|
|
params.runtime.error(
|
|
[
|
|
`--secret-input-mode ref requires an explicit environment variable for provider "${params.provider}".`,
|
|
`Set ${params.envVar} in env and retry, or use --secret-input-mode plaintext.`,
|
|
].join("\n"),
|
|
);
|
|
params.runtime.exit(1);
|
|
return null;
|
|
}
|
|
return { key: resolvedEnvKey, source: "env", envVarName: resolvedEnvVarName };
|
|
}
|
|
}
|
|
|
|
if (flagKey) {
|
|
return { key: flagKey, source: "flag" };
|
|
}
|
|
|
|
if (resolvedEnvKey) {
|
|
return { key: resolvedEnvKey, source: "env", envVarName: resolvedEnvVarName };
|
|
}
|
|
|
|
if (params.allowProfile ?? true) {
|
|
const profileKey = await resolveApiKeyFromProfiles({
|
|
provider: params.provider,
|
|
cfg: params.cfg,
|
|
agentDir: params.agentDir,
|
|
});
|
|
if (profileKey) {
|
|
return { key: profileKey, source: "profile" };
|
|
}
|
|
}
|
|
|
|
if (params.required === false) {
|
|
return null;
|
|
}
|
|
|
|
const profileHint =
|
|
params.allowProfile === false ? "" : `, or existing ${params.provider} API-key profile`;
|
|
params.runtime.error(`Missing ${params.flagName} (or ${params.envVar} in env${profileHint}).`);
|
|
params.runtime.exit(1);
|
|
return null;
|
|
}
|