fix: Remove api key check in auth, ensure thrown in loadModel (#625)

* fix: Remove api key check in auth, ensure thrown in loadModel

* cr

* cr
This commit is contained in:
Brace Sproul 2025-07-31 10:37:24 -07:00 • committed by GitHub
parent 7e9733a156
commit 7a23f5fdcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 122 deletions

View file

@ -5,7 +5,6 @@ import {
verifyGithubUserId,
} from "@open-swe/shared/github/verify-user";
import {
API_KEY_REQUIRED_MESSAGE,
GITHUB_INSTALLATION_ID,
GITHUB_INSTALLATION_NAME,
GITHUB_INSTALLATION_TOKEN_COOKIE,
@ -18,11 +17,6 @@ import { verifyGitHubWebhookOrThrow } from "./github.js";
import { createWithOwnerMetadata, createOwnerFilter } from "./utils.js";
import { LANGGRAPH_USER_PERMISSIONS } from "../constants.js";
import { getGitHubPatFromRequest } from "../utils/github-pat.js";
import { isAllowedUser } from "@open-swe/shared/github/allowed-users";
import { validate } from "uuid";
import { createLogger, LogLevel } from "../utils/logger.js";
const logger = createLogger(LogLevel.INFO, "Auth");
// TODO: Export from LangGraph SDK
export interface BaseAuthReturn {
@ -38,64 +32,6 @@ interface AuthenticateReturn extends BaseAuthReturn {
};
}
function apiKeysInRequestBody(
bodyStr: string | Record<string, unknown>,
): boolean {
logger.info("CHECKING RUN REQ BODY!!", bodyStr);
try {
const body = typeof bodyStr === "string" ? JSON.parse(bodyStr) : bodyStr;
if (
body.config?.configurable &&
("anthropicApiKey" in body.config.configurable.apiKeys ||
"openaiApiKey" in body.config.configurable.apiKeys ||
"googleApiKey" in body.config.configurable.apiKeys)
) {
logger.info("RUN REQ BODY CONTAINS API KEYS!!", bodyStr);
return true;
}
logger.info("RUN REQ BODY DOES NOT CONTAIN API KEYS!!", bodyStr);
return false;
} catch (e: any) {
logger.error("RUN REQ BODY DOES NOT CONTAIN API KEYS!!", {
bodyStr,
error: e,
});
return false;
}
}
function isRunReq(reqUrl: string): boolean {
try {
const url = new URL(reqUrl);
const pathnameParts = url.pathname.split("/");
const isCreateAndWait = !!(
pathnameParts[1] === "threads" &&
validate(pathnameParts[2]) &&
pathnameParts[3] === "runs" &&
pathnameParts[4] === "wait" &&
pathnameParts.length === 5
);
const isCreateBackground = !!(
pathnameParts[1] === "threads" &&
validate(pathnameParts[2]) &&
pathnameParts[3] === "runs" &&
pathnameParts.length === 4
);
const isCreateStream = !!(
pathnameParts[1] === "threads" &&
validate(pathnameParts[2]) &&
pathnameParts[3] === "runs" &&
pathnameParts[4] === "stream" &&
pathnameParts.length === 5
);
return !!isCreateAndWait || !!isCreateBackground || !!isCreateStream;
} catch {
// no-op
return false;
}
}
export const auth = new Auth()
.authenticate<AuthenticateReturn>(async (request: Request) => {
const isProd = process.env.NODE_ENV === "production";
@ -208,26 +144,6 @@ export const auth = new Auth()
});
}
const reqCopy = request.clone();
let reqBody: string | Record<string, unknown>;
try {
reqBody = (await reqCopy.json()) as Record<string, unknown>;
} catch {
reqBody = await reqCopy.text();
}
if (!isAllowedUser(user.login)) {
if (isRunReq(request.url)) {
if (!apiKeysInRequestBody(reqBody)) {
logger.warn("No API keys found in run request body", {
url: request.url,
});
throw new HTTPException(401, {
message: API_KEY_REQUIRED_MESSAGE,
});
}
}
}
return {
identity: user.id.toString(),
is_authenticated: true,

View file

@ -8,6 +8,7 @@ import { Task } from "./constants.js";
import { isAllowedUser } from "@open-swe/shared/github/allowed-users";
import { decryptSecret } from "@open-swe/shared/crypto";
import { TASK_TO_CONFIG_DEFAULTS_MAP } from "./constants.js";
import { API_KEY_REQUIRED_MESSAGE } from "@open-swe/shared/constants";
const logger = createLogger(LogLevel.INFO, "ModelManager");
@ -105,12 +106,55 @@ export class ModelManager {
const model = await this.initializeModel(baseConfig, graphConfig);
return model;
}
private getUserApiKey(
graphConfig: GraphConfig,
provider: Provider,
): string | null {
const userLogin = (graphConfig.configurable as any)?.langgraph_auth_user
?.display_name;
const secretsEncryptionKey = process.env.SECRETS_ENCRYPTION_KEY;
if (!secretsEncryptionKey) {
throw new Error(
"SECRETS_ENCRYPTION_KEY environment variable is required",
);
}
if (!userLogin) {
throw new Error("User login not found in config");
}
// If the user is allowed, we can return early
if (isAllowedUser(userLogin)) {
return null;
}
const apiKeys = graphConfig.configurable?.apiKeys;
if (!apiKeys) {
throw new Error(API_KEY_REQUIRED_MESSAGE);
}
const missingProviderKeyMessage = `No API key found for provider: ${provider}. Please add one in the settings page.`;
const providerApiKey = providerToApiKey(provider, apiKeys);
if (!providerApiKey) {
throw new Error(missingProviderKeyMessage);
}
const apiKey = decryptSecret(providerApiKey, secretsEncryptionKey);
if (!apiKey) {
throw new Error(missingProviderKeyMessage);
}
return apiKey;
}
/**
* Initialize the model instance
*/
public async initializeModel(
config: ModelLoadConfig,
graphConfig?: GraphConfig,
graphConfig: GraphConfig,
) {
const {
provider,
@ -130,42 +174,7 @@ export class ModelManager {
finalMaxTokens = finalMaxTokens > 8_192 ? 8_192 : finalMaxTokens;
}
let apiKey: string | null = null;
if (graphConfig) {
const userLogin = (graphConfig.configurable as any)?.langgraph_auth_user
?.display_name;
const secretsEncryptionKey = process.env.SECRETS_ENCRYPTION_KEY;
if (!secretsEncryptionKey) {
throw new Error(
"SECRETS_ENCRYPTION_KEY environment variable is required",
);
}
if (!userLogin) {
throw new Error("User login not found in config");
}
const apiKeys = graphConfig.configurable?.apiKeys;
if (!isAllowedUser(userLogin)) {
if (!apiKeys) {
throw new Error("API keys not found in config");
}
const providerApiKey = providerToApiKey(provider, apiKeys);
if (!providerApiKey) {
throw new Error(
"No API key found for provider: " +
provider +
". Please add one in the settings page.",
);
}
apiKey = decryptSecret(providerApiKey, secretsEncryptionKey);
if (!apiKey) {
throw new Error(
"No API key found for provider: " +
provider +
". Please add one in the settings page.",
);
}
}
}
const apiKey = this.getUserApiKey(graphConfig, provider);
const modelOptions: InitChatModelArgs = {
modelProvider: provider,

View file

@ -17,6 +17,7 @@ import { ChatResult, ChatGeneration } from "@langchain/core/outputs";
import { BaseLanguageModelInput } from "@langchain/core/language_models/base";
import { BindToolsInput } from "@langchain/core/language_models/chat_models";
import { getMessageContentString } from "@open-swe/shared/messages";
import { getConfig } from "@langchain/langgraph";
const logger = createLogger(LogLevel.DEBUG, "FallbackRunnable");
@ -108,8 +109,13 @@ export class FallbackRunnable<
continue;
}
const graphConfig = getConfig() as GraphConfig;
try {
const model = await this.modelManager.initializeModel(modelConfig);
const model = await this.modelManager.initializeModel(
modelConfig,
graphConfig,
);
let runnableToUse: Runnable<BaseLanguageModelInput, AIMessageChunk> =
model;