mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-23 22:55:24 +00:00
* Models: gate custom provider keys by usable secret semantics * Config: project runtime writes onto source snapshot * Models: prevent stale apiKey preservation for marker-managed providers * Runner: strip SecretRef marker headers from resolved models * Secrets: scan active agent models.json path in audit * Config: guard runtime-source projection for unrelated configs * Extensions: fix onboarding type errors in CI * Tests: align setup helper account-enabled expectation * Secrets audit: harden models.json file reads * fix: harden SecretRef custom/provider secret persistence (#42554) (thanks @joshavant)
160 lines
5.2 KiB
TypeScript
160 lines
5.2 KiB
TypeScript
import { formatRemainingShort } from "../../agents/auth-health.js";
|
|
import {
|
|
type AuthProfileStore,
|
|
listProfilesForProvider,
|
|
resolveAuthProfileDisplayLabel,
|
|
resolveAuthStorePathForDisplay,
|
|
resolveProfileUnusableUntilForDisplay,
|
|
} from "../../agents/auth-profiles.js";
|
|
import { isNonSecretApiKeyMarker } from "../../agents/model-auth-markers.js";
|
|
import {
|
|
getCustomProviderApiKey,
|
|
resolveEnvApiKey,
|
|
resolveUsableCustomProviderApiKey,
|
|
} from "../../agents/model-auth.js";
|
|
import type { OpenClawConfig } from "../../config/config.js";
|
|
import { shortenHomePath } from "../../utils.js";
|
|
import { maskApiKey } from "./list.format.js";
|
|
import type { ProviderAuthOverview } from "./list.types.js";
|
|
|
|
function formatMarkerOrSecret(value: string): string {
|
|
return isNonSecretApiKeyMarker(value, { includeEnvVarName: false })
|
|
? `marker(${value.trim()})`
|
|
: maskApiKey(value);
|
|
}
|
|
|
|
function formatProfileSecretLabel(params: {
|
|
value: string | undefined;
|
|
ref: { source: string; id: string } | undefined;
|
|
kind: "api-key" | "token";
|
|
}): string {
|
|
const value = typeof params.value === "string" ? params.value.trim() : "";
|
|
if (value) {
|
|
const display = formatMarkerOrSecret(value);
|
|
return params.kind === "token" ? `token:${display}` : display;
|
|
}
|
|
if (params.ref) {
|
|
const refLabel = `ref(${params.ref.source}:${params.ref.id})`;
|
|
return params.kind === "token" ? `token:${refLabel}` : refLabel;
|
|
}
|
|
return params.kind === "token" ? "token:missing" : "missing";
|
|
}
|
|
|
|
export function resolveProviderAuthOverview(params: {
|
|
provider: string;
|
|
cfg: OpenClawConfig;
|
|
store: AuthProfileStore;
|
|
modelsPath: string;
|
|
}): ProviderAuthOverview {
|
|
const { provider, cfg, store } = params;
|
|
const now = Date.now();
|
|
const profiles = listProfilesForProvider(store, provider);
|
|
const withUnusableSuffix = (base: string, profileId: string) => {
|
|
const unusableUntil = resolveProfileUnusableUntilForDisplay(store, profileId);
|
|
if (!unusableUntil || now >= unusableUntil) {
|
|
return base;
|
|
}
|
|
const stats = store.usageStats?.[profileId];
|
|
const kind =
|
|
typeof stats?.disabledUntil === "number" && now < stats.disabledUntil
|
|
? `disabled${stats.disabledReason ? `:${stats.disabledReason}` : ""}`
|
|
: "cooldown";
|
|
const remaining = formatRemainingShort(unusableUntil - now);
|
|
return `${base} [${kind} ${remaining}]`;
|
|
};
|
|
const labels = profiles.map((profileId) => {
|
|
const profile = store.profiles[profileId];
|
|
if (!profile) {
|
|
return `${profileId}=missing`;
|
|
}
|
|
if (profile.type === "api_key") {
|
|
return withUnusableSuffix(
|
|
`${profileId}=${formatProfileSecretLabel({
|
|
value: profile.key,
|
|
ref: profile.keyRef,
|
|
kind: "api-key",
|
|
})}`,
|
|
profileId,
|
|
);
|
|
}
|
|
if (profile.type === "token") {
|
|
return withUnusableSuffix(
|
|
`${profileId}=${formatProfileSecretLabel({
|
|
value: profile.token,
|
|
ref: profile.tokenRef,
|
|
kind: "token",
|
|
})}`,
|
|
profileId,
|
|
);
|
|
}
|
|
const display = resolveAuthProfileDisplayLabel({ cfg, store, profileId });
|
|
const suffix =
|
|
display === profileId
|
|
? ""
|
|
: display.startsWith(profileId)
|
|
? display.slice(profileId.length).trim()
|
|
: `(${display})`;
|
|
const base = `${profileId}=OAuth${suffix ? ` ${suffix}` : ""}`;
|
|
return withUnusableSuffix(base, profileId);
|
|
});
|
|
const oauthCount = profiles.filter((id) => store.profiles[id]?.type === "oauth").length;
|
|
const tokenCount = profiles.filter((id) => store.profiles[id]?.type === "token").length;
|
|
const apiKeyCount = profiles.filter((id) => store.profiles[id]?.type === "api_key").length;
|
|
|
|
const envKey = resolveEnvApiKey(provider);
|
|
const customKey = getCustomProviderApiKey(cfg, provider);
|
|
const usableCustomKey = resolveUsableCustomProviderApiKey({ cfg, provider });
|
|
|
|
const effective: ProviderAuthOverview["effective"] = (() => {
|
|
if (profiles.length > 0) {
|
|
return {
|
|
kind: "profiles",
|
|
detail: shortenHomePath(resolveAuthStorePathForDisplay()),
|
|
};
|
|
}
|
|
if (envKey) {
|
|
const isOAuthEnv =
|
|
envKey.source.includes("OAUTH_TOKEN") || envKey.source.toLowerCase().includes("oauth");
|
|
return {
|
|
kind: "env",
|
|
detail: isOAuthEnv ? "OAuth (env)" : maskApiKey(envKey.apiKey),
|
|
};
|
|
}
|
|
if (usableCustomKey) {
|
|
return { kind: "models.json", detail: formatMarkerOrSecret(usableCustomKey.apiKey) };
|
|
}
|
|
return { kind: "missing", detail: "missing" };
|
|
})();
|
|
|
|
return {
|
|
provider,
|
|
effective,
|
|
profiles: {
|
|
count: profiles.length,
|
|
oauth: oauthCount,
|
|
token: tokenCount,
|
|
apiKey: apiKeyCount,
|
|
labels,
|
|
},
|
|
...(envKey
|
|
? {
|
|
env: {
|
|
value:
|
|
envKey.source.includes("OAUTH_TOKEN") || envKey.source.toLowerCase().includes("oauth")
|
|
? "OAuth (env)"
|
|
: maskApiKey(envKey.apiKey),
|
|
source: envKey.source,
|
|
},
|
|
}
|
|
: {}),
|
|
...(customKey
|
|
? {
|
|
modelsJson: {
|
|
value: formatMarkerOrSecret(customKey),
|
|
source: `models.json: ${shortenHomePath(params.modelsPath)}`,
|
|
},
|
|
}
|
|
: {}),
|
|
};
|
|
}
|