Files
openclaw/extensions/github-copilot/auth.ts
Peter Steinberger 992948356f fix(secrets): fail closed for configured references (#127669)
* fix(secrets): fail closed for configured references

Keep explicit SecretRefs authoritative across Gateway auth, onboarding, probes, channel credentials, outbound sends, and GitHub Copilot discovery/embeddings. Ambient credentials remain available only when no reference owns the surface.

* test(secrets): align gateway fallback expectations

* test(providers): remove load-sensitive stream timing

* test(xai): normalize stream capture failures

* fix(copilot): preserve direct auth precedence

* test(ollama): keep progressing streams alive

* test(models): mark resolved config fixtures

* fix(models): drop stale probe import
2026-08-21 18:00:28 -07:00

91 lines
3.5 KiB
TypeScript

// Github Copilot plugin module implements auth behavior.
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type { ProviderPrepareDynamicModelContext } from "openclaw/plugin-sdk/plugin-entry";
import {
coerceSecretRef,
ensureAuthProfileStore,
listProfilesForProvider,
normalizeOptionalSecretInput,
} from "openclaw/plugin-sdk/provider-auth";
import {
resolveConfiguredSecretInputWithFallback,
resolveRequiredConfiguredSecretRefInputString,
} from "openclaw/plugin-sdk/secret-input-runtime";
import { PROVIDER_ID } from "./models.js";
export async function resolveFirstGithubToken(params: {
agentDir?: string;
config?: OpenClawConfig;
env: NodeJS.ProcessEnv;
profileId?: string;
authProfileMode?: ProviderPrepareDynamicModelContext["authProfileMode"];
}): Promise<{
githubToken: string;
hasProfile: boolean;
}> {
const authStore = ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
});
const profileIds = listProfilesForProvider(authStore, PROVIDER_ID);
const hasProfile = profileIds.length > 0;
const requestedProfileId = params.profileId?.trim();
const githubToken =
[params.env.COPILOT_GITHUB_TOKEN, params.env.GH_TOKEN, params.env.GITHUB_TOKEN]
.map((value) => normalizeOptionalSecretInput(value))
.find((value) => value !== undefined) ?? "";
const providerConfig = params.config?.models?.providers?.[PROVIDER_ID];
const configuredRefCanOwnAuth =
providerConfig?.auth === undefined ||
providerConfig.auth === "api-key" ||
providerConfig.auth === "token";
const preferConfiguredToken =
(configuredRefCanOwnAuth &&
Boolean(coerceSecretRef(providerConfig?.apiKey, params.config?.secrets?.defaults))) ||
(providerConfig?.auth === "api-key" &&
Boolean(normalizeOptionalSecretInput(providerConfig.apiKey)));
if (
!requestedProfileId &&
(params.authProfileMode || preferConfiguredToken || githubToken || !hasProfile)
) {
// Prepared direct-auth attempts must not borrow a stored profile: model
// limits and the later runtime exchange must use the same source token.
if (githubToken && !preferConfiguredToken) {
return { githubToken, hasProfile: false };
}
if (!params.config) {
return { githubToken: "", hasProfile: false };
}
const resolved = await resolveConfiguredSecretInputWithFallback({
config: params.config,
env: params.env,
value: configuredRefCanOwnAuth
? providerConfig?.apiKey
: normalizeOptionalSecretInput(providerConfig?.apiKey),
path: `models.providers.${PROVIDER_ID}.apiKey`,
readFallback: () => "",
});
if (resolved.secretRefConfigured && !resolved.value) {
throw new Error(
resolved.unresolvedRefReason ??
`models.providers.${PROVIDER_ID}.apiKey SecretRef is unresolved.`,
);
}
return { githubToken: resolved.value?.trim() || githubToken, hasProfile: false };
}
const profileId = requestedProfileId
? profileIds.find((candidate) => candidate === requestedProfileId)
: profileIds[0];
const profile = profileId ? authStore.profiles[profileId] : undefined;
if (profile?.type !== "token") {
return { githubToken: "", hasProfile };
}
const resolved = await resolveRequiredConfiguredSecretRefInputString({
config: params.config ?? {},
env: params.env,
value: profile.tokenRef,
path: `providers.github-copilot.authProfiles.${profileId ?? "default"}.tokenRef`,
});
return { githubToken: (resolved ?? profile.token ?? "").trim(), hasProfile };
}