fix(plugins): strip profileId/preferredProfile from plugin modelAuth wrappers

Address Aisle CWE-862: plugins could use profileId to resolve
credentials for arbitrary profiles regardless of provider, enabling
cross-provider credential access.

Now plugins can only specify provider/model — the core auth pipeline
picks the appropriate credential. The TypeScript type is also narrowed
so plugin authors cannot pass profileId at compile time.
This commit is contained in:
Xinhua Gu
2026-03-09 19:54:40 +01:00
committed by Josh Lehman
parent 8e0fd21e73
commit ee96e96bb9
2 changed files with 16 additions and 10 deletions

View File

@@ -64,23 +64,21 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}):
logging: createRuntimeLogging(),
state: { resolveStateDir },
modelAuth: {
// Wrap model-auth helpers to prevent plugins from passing arbitrary
// agentDir / store overrides, which would let them steer credential
// lookups outside their own context. Only provider, model, cfg, and
// profileId are forwarded.
// Wrap model-auth helpers so plugins cannot steer credential lookups:
// - agentDir / store: stripped (prevents reading other agents' stores)
// - profileId / preferredProfile: stripped (prevents cross-provider
// credential access via profile steering)
// Plugins only specify provider/model; the core auth pipeline picks
// the appropriate credential automatically.
getApiKeyForModel: (params) =>
getApiKeyForModelRaw({
model: params.model,
cfg: params.cfg,
profileId: params.profileId,
preferredProfile: params.preferredProfile,
}),
resolveApiKeyForProvider: (params) =>
resolveApiKeyForProviderRaw({
provider: params.provider,
cfg: params.cfg,
profileId: params.profileId,
preferredProfile: params.preferredProfile,
}),
},
} satisfies PluginRuntime;

View File

@@ -53,7 +53,15 @@ export type PluginRuntimeCore = {
resolveStateDir: typeof import("../../config/paths.js").resolveStateDir;
};
modelAuth: {
getApiKeyForModel: typeof import("../../agents/model-auth.js").getApiKeyForModel;
resolveApiKeyForProvider: typeof import("../../agents/model-auth.js").resolveApiKeyForProvider;
/** Resolve auth for a model. Only provider/model and optional cfg are used. */
getApiKeyForModel: (params: {
model: import("@mariozechner/pi-ai").Model<import("@mariozechner/pi-ai").Api>;
cfg?: import("../../config/config.js").OpenClawConfig;
}) => Promise<import("../../agents/model-auth.js").ResolvedProviderAuth>;
/** Resolve auth for a provider by name. Only provider and optional cfg are used. */
resolveApiKeyForProvider: (params: {
provider: string;
cfg?: import("../../config/config.js").OpenClawConfig;
}) => Promise<import("../../agents/model-auth.js").ResolvedProviderAuth>;
};
};