From abc984409ff53763f745554b22b93c552ef00650 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 11 Jul 2025 10:48:14 -0700 Subject: [PATCH] refactor: Proxy req utils, rename GITHUB_TOKEN_ENCRYPTION_KEY to SECRETS_ENCRYPTION_KEY (#391) * refactor: Proxy req utils, rename GITHUB_TOKEN_ENCRYPTION_KEY to SECRETS_ENCRYPTION_KEY * cr --- README.md | 10 +- apps/docs/setup/authentication.mdx | 6 +- apps/docs/setup/development.mdx | 8 +- apps/open-swe/.env.example | 8 +- .../src/routes/github/issue-webhook.ts | 12 +- apps/open-swe/src/security/auth.ts | 12 +- apps/open-swe/src/utils/github-tokens.ts | 12 +- apps/web/.env.example | 8 +- apps/web/src/app/api/[..._path]/route.ts | 153 +----------------- apps/web/src/app/api/[..._path]/utils.ts | 145 +++++++++++++++++ packages/shared/src/crypto.ts | 41 +++-- 11 files changed, 207 insertions(+), 208 deletions(-) create mode 100644 apps/web/src/app/api/[..._path]/utils.ts diff --git a/README.md b/README.md index e18491e0..98a40fe1 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,10 @@ GOOGLE_API_KEY="" # Daytona API key for accessing and modifying the code in the cloud sandbox. DAYTONA_API_KEY="" -# Encryption key for GitHub tokens (32-byte hex string for AES-256) +# Encryption key for secrets (32-byte hex string for AES-256) # Should be the same value as the one used in the web app. # Can be generated via: `openssl rand -hex 32` -GITHUB_TOKEN_ENCRYPTION_KEY="" +SECRETS_ENCRYPTION_KEY="" # Used for setting the git user name & email for commits. GITHUB_APP_NAME="open-swe-dev" @@ -75,13 +75,13 @@ GITHUB_APP_NAME="open-swe-dev" GITHUB_APP_ID="" GITHUB_APP_PRIVATE_KEY="" -# Encryption key for GitHub tokens (32-byte hex string for AES-256) +# Encryption key for secrets (32-byte hex string for AES-256) # Should be the same value as the one used in the open-swe app. # Can be generated via: `openssl rand -hex 32` -GITHUB_TOKEN_ENCRYPTION_KEY="" +SECRETS_ENCRYPTION_KEY="" ``` -**REMINDER**: The `GITHUB_TOKEN_ENCRYPTION_KEY` environment variable must be the same in both the web and open-swe apps. +**REMINDER**: The `SECRETS_ENCRYPTION_KEY` environment variable must be the same in both the web and open-swe apps. To get the GitHub App secrets, first create a new GitHub app (note: this is not the same as the OAuth app) in [the developer settings](https://github.com/settings/apps/new). diff --git a/apps/docs/setup/authentication.mdx b/apps/docs/setup/authentication.mdx index 01af3267..cf42d947 100644 --- a/apps/docs/setup/authentication.mdx +++ b/apps/docs/setup/authentication.mdx @@ -47,12 +47,12 @@ All headers are prefixed with `x-` to ensure they're included in LangGraph run c ### Token Encryption -Open SWE implements AES-256-GCM encryption for all GitHub tokens to prevent exposure in: +Open SWE implements AES-256-GCM encryption for all secrets passed to the LangGraph server to prevent exposure in: - LangSmith trace metadata - Run configurations - Potential unauthorized access scenarios -The encryption process uses the `GITHUB_TOKEN_ENCRYPTION_KEY` environment variable and includes: +The encryption process uses the `SECRETS_ENCRYPTION_KEY` environment variable and includes: - **Initialization Vector (IV)** for unique encryption per token - **Authentication Tag** for data integrity verification - **Base64 encoding** for safe transport @@ -135,5 +135,5 @@ This design ensures tokens remain encrypted in storage and traces while being av -Always ensure the `GITHUB_TOKEN_ENCRYPTION_KEY` environment variable is identical between your web application and LangGraph agent deployments. +Always ensure the `SECRETS_ENCRYPTION_KEY` environment variable is identical between your web application and LangGraph agent deployments. diff --git a/apps/docs/setup/development.mdx b/apps/docs/setup/development.mdx index 96de70f3..772014ab 100644 --- a/apps/docs/setup/development.mdx +++ b/apps/docs/setup/development.mdx @@ -62,8 +62,8 @@ Before starting, ensure you have the following installed: GITHUB_APP_CLIENT_SECRET="" GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback" - # Token encryption key (generate with: openssl rand -hex 32) - GITHUB_TOKEN_ENCRYPTION_KEY="" + # Encryption key for secrets (generate with: openssl rand -hex 32) + SECRETS_ENCRYPTION_KEY="" # GitHub App details (will be filled after creating GitHub App) GITHUB_APP_NAME="open-swe-dev" @@ -101,7 +101,6 @@ Before starting, ensure you have the following installed: FIRECRAWL_API_KEY="" # For URL content extraction # GitHub App settings (same as web app) - GITHUB_TOKEN_ENCRYPTION_KEY="" # Must match web app value GITHUB_APP_NAME="open-swe-dev" GITHUB_APP_ID="" GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- @@ -113,10 +112,11 @@ Before starting, ensure you have the following installed: # Server configuration PORT="2024" OPEN_SWE_APP_URL="http://localhost:3000" + SECRETS_ENCRYPTION_KEY="" # Must match web app value ``` - Generate the `GITHUB_TOKEN_ENCRYPTION_KEY` using: `openssl rand -hex 32`. This key must be identical in both environment files. + Generate the `SECRETS_ENCRYPTION_KEY` using: `openssl rand -hex 32`. This key must be identical in both environment files. diff --git a/apps/open-swe/.env.example b/apps/open-swe/.env.example index 17eeb803..c3d38e46 100644 --- a/apps/open-swe/.env.example +++ b/apps/open-swe/.env.example @@ -25,10 +25,6 @@ FIRECRAWL_API_KEY="" # ------------------Github App Secrets----------------- -# Encryption key for GitHub tokens (32-byte hex string for AES-256) -# Should be the same value as the one used in the web app, so that tokens -# encrypted in the web app can be decrypted in the agent. -GITHUB_TOKEN_ENCRYPTION_KEY="" # Used for setting the git user name & email for commits. # Can modify to whatever string you want. GITHUB_APP_NAME="open-swe-dev" @@ -50,3 +46,7 @@ PORT="2024" # Should be the URL of the web app. Localhost in dev, production URL # in production. OPEN_SWE_APP_URL="http://localhost:3000" +# Encryption key for secrets (32-byte hex string for AES-256) +# Should be the same value as the one used in the web app, so that secrets +# encrypted in the web app can be decrypted in the agent. +SECRETS_ENCRYPTION_KEY="" diff --git a/apps/open-swe/src/routes/github/issue-webhook.ts b/apps/open-swe/src/routes/github/issue-webhook.ts index 0ba94aa5..ea6bf95c 100644 --- a/apps/open-swe/src/routes/github/issue-webhook.ts +++ b/apps/open-swe/src/routes/github/issue-webhook.ts @@ -12,7 +12,7 @@ import { GITHUB_USER_LOGIN_HEADER, MANAGER_GRAPH_ID, } from "@open-swe/shared/constants"; -import { encryptGitHubToken } from "@open-swe/shared/crypto"; +import { encryptSecret } from "@open-swe/shared/crypto"; import { HumanMessage } from "@langchain/core/messages"; import { getOpenSWEAutoAcceptLabel, @@ -86,10 +86,8 @@ const getHeaders = ( }; webhooks.on("issues.labeled", async ({ payload }) => { - if (!process.env.GITHUB_TOKEN_ENCRYPTION_KEY) { - throw new Error( - "GITHUB_TOKEN_ENCRYPTION_KEY environment variable is required", - ); + if (!process.env.SECRETS_ENCRYPTION_KEY) { + throw new Error("SECRETS_ENCRYPTION_KEY environment variable is required"); } const validOpenSWELabels = [getOpenSWELabel(), getOpenSWEAutoAcceptLabel()]; if ( @@ -132,9 +130,9 @@ webhooks.on("issues.labeled", async ({ payload }) => { const langGraphClient = createLangGraphClient({ defaultHeaders: { - [GITHUB_INSTALLATION_TOKEN_COOKIE]: encryptGitHubToken( + [GITHUB_INSTALLATION_TOKEN_COOKIE]: encryptSecret( token, - process.env.GITHUB_TOKEN_ENCRYPTION_KEY, + process.env.SECRETS_ENCRYPTION_KEY, ), [GITHUB_INSTALLATION_NAME]: issueData.owner, [GITHUB_USER_ID_HEADER]: issueData.userId.toString(), diff --git a/apps/open-swe/src/security/auth.ts b/apps/open-swe/src/security/auth.ts index 0c0cf075..8da8782c 100644 --- a/apps/open-swe/src/security/auth.ts +++ b/apps/open-swe/src/security/auth.ts @@ -11,7 +11,7 @@ import { GITHUB_USER_ID_HEADER, GITHUB_USER_LOGIN_HEADER, } from "@open-swe/shared/constants"; -import { decryptGitHubToken } from "@open-swe/shared/crypto"; +import { decryptSecret } from "@open-swe/shared/crypto"; import { verifyGitHubWebhookOrThrow } from "./github.js"; import { createWithOwnerMetadata, createOwnerFilter } from "./utils.js"; @@ -49,11 +49,9 @@ export const auth = new Auth() return await verifyGitHubWebhookOrThrow(request); } - const encryptionKey = process.env.GITHUB_TOKEN_ENCRYPTION_KEY; + const encryptionKey = process.env.SECRETS_ENCRYPTION_KEY; if (!encryptionKey) { - throw new Error( - "Missing GITHUB_TOKEN_ENCRYPTION_KEY environment variable.", - ); + throw new Error("Missing SECRETS_ENCRYPTION_KEY environment variable."); } const installationNameHeader = request.headers.get( @@ -90,14 +88,14 @@ export const auth = new Auth() }); } user = await verifyGithubUserId( - decryptGitHubToken(encryptedInstallationToken, encryptionKey), + decryptSecret(encryptedInstallationToken, encryptionKey), Number(userIdHeader), userLoginHeader, ); } else { // Ensure we decrypt the token before passing to the verification function. user = await verifyGithubUser( - decryptGitHubToken(encryptedAccessToken, encryptionKey), + decryptSecret(encryptedAccessToken, encryptionKey), ); } diff --git a/apps/open-swe/src/utils/github-tokens.ts b/apps/open-swe/src/utils/github-tokens.ts index 15a2d8f7..df141ce6 100644 --- a/apps/open-swe/src/utils/github-tokens.ts +++ b/apps/open-swe/src/utils/github-tokens.ts @@ -3,7 +3,7 @@ import { GITHUB_INSTALLATION_TOKEN_COOKIE, } from "@open-swe/shared/constants"; import { GraphConfig } from "@open-swe/shared/open-swe/types"; -import { decryptGitHubToken } from "@open-swe/shared/crypto"; +import { decryptSecret } from "@open-swe/shared/crypto"; export function getGitHubTokensFromConfig(config: GraphConfig): { githubAccessToken: string; @@ -22,18 +22,16 @@ export function getGitHubTokensFromConfig(config: GraphConfig): { } // Get the encryption key from environment variables - const encryptionKey = process.env.GITHUB_TOKEN_ENCRYPTION_KEY; + const encryptionKey = process.env.SECRETS_ENCRYPTION_KEY; if (!encryptionKey) { - throw new Error( - "Missing GITHUB_TOKEN_ENCRYPTION_KEY environment variable.", - ); + throw new Error("Missing SECRETS_ENCRYPTION_KEY environment variable."); } // Decrypt the GitHub token const githubAccessToken = encryptedGitHubToken - ? decryptGitHubToken(encryptedGitHubToken, encryptionKey) + ? decryptSecret(encryptedGitHubToken, encryptionKey) : ""; - const githubInstallationToken = decryptGitHubToken( + const githubInstallationToken = decryptSecret( encryptedInstallationToken, encryptionKey, ); diff --git a/apps/web/.env.example b/apps/web/.env.example index f5f0f091..4628f665 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -7,10 +7,6 @@ GITHUB_APP_CLIENT_SECRET="" # your GitHub app settings. GITHUB_APP_REDIRECT_URI="http://localhost:3000/api/auth/github/callback" -# Encryption key for GitHub tokens (32-byte hex string for AES-256) -# Should be the same value as the one used in the web app, so that tokens -# encrypted in the web app can be decrypted in the agent. -GITHUB_TOKEN_ENCRYPTION_KEY="" # Used for setting the git user name & email for commits. # Can modify to whatever string you want. GITHUB_APP_NAME="open-swe-dev" @@ -29,3 +25,7 @@ NEXT_PUBLIC_API_URL="http://localhost:3000/api" # The API URL of the LangGraph server. Used in the proxy route to forward # requests to the LangGraph server. LANGGRAPH_API_URL="http://localhost:2024" +# Encryption key for secrets (32-byte hex string for AES-256) +# Should be the same value as the one used in the web app, so that secrets +# encrypted in the web app can be decrypted in the agent. +SECRETS_ENCRYPTION_KEY="" diff --git a/apps/web/src/app/api/[..._path]/route.ts b/apps/web/src/app/api/[..._path]/route.ts index fb1d7fb1..d22e3bee 100644 --- a/apps/web/src/app/api/[..._path]/route.ts +++ b/apps/web/src/app/api/[..._path]/route.ts @@ -5,150 +5,11 @@ import { GITHUB_INSTALLATION_TOKEN_COOKIE, GITHUB_INSTALLATION_NAME, } from "@open-swe/shared/constants"; -import { encryptGitHubToken } from "@open-swe/shared/crypto"; -import { NextRequest } from "next/server"; -import { getInstallationToken } from "@/utils/github"; -import { App } from "@octokit/app"; -import { validate } from "uuid"; - -function getGitHubAccessTokenOrThrow( - req: NextRequest, - encryptionKey: string, -): string { - const token = req.cookies.get(GITHUB_TOKEN_COOKIE)?.value ?? ""; - - if (!token) { - throw new Error( - "No GitHub access token found. User must authenticate first.", - ); - } - - return encryptGitHubToken(token, encryptionKey); -} - -async function getGitHubInstallationTokenOrThrow( - installationIdCookie: string, - encryptionKey: string, -): Promise { - const appId = process.env.GITHUB_APP_ID; - const privateAppKey = process.env.GITHUB_APP_PRIVATE_KEY; - - if (!appId || !privateAppKey) { - throw new Error("GitHub App ID or Private App Key is not configured."); - } - - const token = await getInstallationToken( - installationIdCookie, - appId, - privateAppKey, - ); - return encryptGitHubToken(token, encryptionKey); -} - -async function getInstallationName(installationId: string) { - if (!process.env.GITHUB_APP_ID || !process.env.GITHUB_APP_PRIVATE_KEY) { - throw new Error("GitHub App ID or Private App Key is not configured."); - } - const app = new App({ - appId: process.env.GITHUB_APP_ID, - privateKey: process.env.GITHUB_APP_PRIVATE_KEY, - }); - - // Get installation details - const { data } = await app.octokit.request( - "GET /app/installations/{installation_id}", - { - installation_id: Number(installationId), - }, - ); - - const installationName = - data.account && "name" in data.account - ? data.account.name - : data.account?.login; - - return installationName ?? ""; -} - -const isNewRunRequest = (reqUrlStr: string, reqMethod: string) => { - try { - const reqPathnameParts = new URL(reqUrlStr).pathname.split("/"); - const isCreateNewRunReq = - reqPathnameParts?.[1] === "api" && - reqPathnameParts?.[2] === "threads" && - validate(reqPathnameParts?.[3]) && - reqPathnameParts?.[4] === "runs" && - reqMethod.toLowerCase() === "post"; - const isStreamRunReq = - reqPathnameParts?.[1] === "api" && - reqPathnameParts?.[2] === "threads" && - validate(reqPathnameParts?.[3]) && - reqPathnameParts?.[4] === "runs" && - validate(reqPathnameParts?.[5]) && - reqPathnameParts?.[6]?.startsWith("stream") && - reqMethod.toLowerCase() === "get"; - return isCreateNewRunReq || isStreamRunReq; - } catch { - return false; - } -}; - -const isGetStateRequest = (reqUrlStr: string, reqMethod: string) => { - try { - const reqPathnameParts = new URL(reqUrlStr).pathname.split("/"); - const isGetStateReq = - reqPathnameParts?.[1] === "api" && - reqPathnameParts?.[2] === "threads" && - validate(reqPathnameParts?.[3]) && - reqPathnameParts?.[4] === "state" && - reqMethod.toLowerCase() === "get"; - return isGetStateReq; - } catch { - return false; - } -}; - -const isSearchThreadsRequest = (reqUrlStr: string, reqMethod: string) => { - try { - const reqPathnameParts = new URL(reqUrlStr).pathname.split("/"); - const isGetStateReq = - reqPathnameParts?.[1] === "api" && - reqPathnameParts?.[2] === "threads" && - reqPathnameParts?.[3] === "search" && - reqMethod.toLowerCase() === "post"; - return isGetStateReq; - } catch { - return false; - } -}; - -async function getInstallationNameFromReq( - req: Request, - installationId: string, -): Promise { - try { - const requestJson = await req.json(); - const installationName = requestJson?.input?.targetRepository?.owner; - if (installationName) { - return installationName; - } - } catch { - // no-op - } - - try { - if ( - isNewRunRequest(req.url, req.method) || - isGetStateRequest(req.url, req.method) || - isSearchThreadsRequest(req.url, req.method) - ) { - return await getInstallationName(installationId); - } - return ""; - } catch { - return ""; - } -} +import { + getGitHubInstallationTokenOrThrow, + getInstallationNameFromReq, + getGitHubAccessTokenOrThrow, +} from "./utils"; // This file acts as a proxy for requests to your LangGraph server. // Read the [Going to Production](https://github.com/langchain-ai/agent-chat-ui?tab=readme-ov-file#going-to-production) section for more information. @@ -159,10 +20,10 @@ export const { GET, POST, PUT, PATCH, DELETE, OPTIONS, runtime } = runtime: "edge", // default disableWarningLog: true, headers: async (req) => { - const encryptionKey = process.env.GITHUB_TOKEN_ENCRYPTION_KEY; + const encryptionKey = process.env.SECRETS_ENCRYPTION_KEY; if (!encryptionKey) { throw new Error( - "GITHUB_TOKEN_ENCRYPTION_KEY environment variable is required", + "SECRETS_ENCRYPTION_KEY environment variable is required", ); } const installationIdCookie = req.cookies.get( diff --git a/apps/web/src/app/api/[..._path]/utils.ts b/apps/web/src/app/api/[..._path]/utils.ts new file mode 100644 index 00000000..c14d2ce1 --- /dev/null +++ b/apps/web/src/app/api/[..._path]/utils.ts @@ -0,0 +1,145 @@ +import { getInstallationToken } from "@/utils/github"; +import { App } from "@octokit/app"; +import { GITHUB_TOKEN_COOKIE } from "@open-swe/shared/constants"; +import { encryptSecret } from "@open-swe/shared/crypto"; +import { NextRequest } from "next/server"; +import { validate } from "uuid"; + +export function getGitHubAccessTokenOrThrow( + req: NextRequest, + encryptionKey: string, +): string { + const token = req.cookies.get(GITHUB_TOKEN_COOKIE)?.value ?? ""; + + if (!token) { + throw new Error( + "No GitHub access token found. User must authenticate first.", + ); + } + + return encryptSecret(token, encryptionKey); +} + +export async function getGitHubInstallationTokenOrThrow( + installationIdCookie: string, + encryptionKey: string, +): Promise { + const appId = process.env.GITHUB_APP_ID; + const privateAppKey = process.env.GITHUB_APP_PRIVATE_KEY; + + if (!appId || !privateAppKey) { + throw new Error("GitHub App ID or Private App Key is not configured."); + } + + const token = await getInstallationToken( + installationIdCookie, + appId, + privateAppKey, + ); + return encryptSecret(token, encryptionKey); +} + +async function getInstallationName(installationId: string) { + if (!process.env.GITHUB_APP_ID || !process.env.GITHUB_APP_PRIVATE_KEY) { + throw new Error("GitHub App ID or Private App Key is not configured."); + } + const app = new App({ + appId: process.env.GITHUB_APP_ID, + privateKey: process.env.GITHUB_APP_PRIVATE_KEY, + }); + + // Get installation details + const { data } = await app.octokit.request( + "GET /app/installations/{installation_id}", + { + installation_id: Number(installationId), + }, + ); + + const installationName = + data.account && "name" in data.account + ? data.account.name + : data.account?.login; + + return installationName ?? ""; +} + +function isNewRunRequest(reqUrlStr: string, reqMethod: string) { + try { + const reqPathnameParts = new URL(reqUrlStr).pathname.split("/"); + const isCreateNewRunReq = + reqPathnameParts?.[1] === "api" && + reqPathnameParts?.[2] === "threads" && + validate(reqPathnameParts?.[3]) && + reqPathnameParts?.[4] === "runs" && + reqMethod.toLowerCase() === "post"; + const isStreamRunReq = + reqPathnameParts?.[1] === "api" && + reqPathnameParts?.[2] === "threads" && + validate(reqPathnameParts?.[3]) && + reqPathnameParts?.[4] === "runs" && + validate(reqPathnameParts?.[5]) && + reqPathnameParts?.[6]?.startsWith("stream") && + reqMethod.toLowerCase() === "get"; + return isCreateNewRunReq || isStreamRunReq; + } catch { + return false; + } +} + +function isGetStateRequest(reqUrlStr: string, reqMethod: string) { + try { + const reqPathnameParts = new URL(reqUrlStr).pathname.split("/"); + const isGetStateReq = + reqPathnameParts?.[1] === "api" && + reqPathnameParts?.[2] === "threads" && + validate(reqPathnameParts?.[3]) && + reqPathnameParts?.[4] === "state" && + reqMethod.toLowerCase() === "get"; + return isGetStateReq; + } catch { + return false; + } +} + +function isSearchThreadsRequest(reqUrlStr: string, reqMethod: string) { + try { + const reqPathnameParts = new URL(reqUrlStr).pathname.split("/"); + const isGetStateReq = + reqPathnameParts?.[1] === "api" && + reqPathnameParts?.[2] === "threads" && + reqPathnameParts?.[3] === "search" && + reqMethod.toLowerCase() === "post"; + return isGetStateReq; + } catch { + return false; + } +} + +export async function getInstallationNameFromReq( + req: Request, + installationId: string, +): Promise { + try { + const requestJson = await req.json(); + const installationName = requestJson?.input?.targetRepository?.owner; + if (installationName) { + return installationName; + } + } catch { + // no-op + } + + try { + if ( + isNewRunRequest(req.url, req.method) || + isGetStateRequest(req.url, req.method) || + isSearchThreadsRequest(req.url, req.method) + ) { + return await getInstallationName(installationId); + } + return ""; + } catch { + return ""; + } +} diff --git a/packages/shared/src/crypto.ts b/packages/shared/src/crypto.ts index e3cc44af..b28f2718 100644 --- a/packages/shared/src/crypto.ts +++ b/packages/shared/src/crypto.ts @@ -20,19 +20,16 @@ function deriveKey(encryptionKey: string): Buffer { } /** - * Encrypts a GitHub token using AES-256-GCM + * Encrypts a secret using AES-256-GCM * - * @param token - The GitHub access token to encrypt + * @param secret - The secret to encrypt * @param encryptionKey - The encryption key (will be hashed to 256 bits) * @returns Base64 encoded encrypted data containing IV, encrypted token, and auth tag * @throws Error if encryption fails or inputs are invalid */ -export function encryptGitHubToken( - token: string, - encryptionKey: string, -): string { - if (!token || typeof token !== "string") { - throw new Error("Token must be a non-empty string"); +export function encryptSecret(secret: string, encryptionKey: string): string { + if (!secret || typeof secret !== "string") { + throw new Error("Secret must be a non-empty string"); } if (!encryptionKey || typeof encryptionKey !== "string") { @@ -49,9 +46,9 @@ export function encryptGitHubToken( // Create cipher const cipher = crypto.createCipheriv(ALGORITHM, key, iv); - // Encrypt the token + // Encrypt the secret const encryptedBuffer = Buffer.concat([ - cipher.update(token, "utf8"), + cipher.update(secret, "utf8"), cipher.final(), ]); @@ -64,25 +61,25 @@ export function encryptGitHubToken( return combined.toString("base64"); } catch (error) { throw new Error( - `Failed to encrypt token: ${error instanceof Error ? error.message : "Unknown error"}`, + `Failed to encrypt secret: ${error instanceof Error ? error.message : "Unknown error"}`, ); } } /** - * Decrypts a GitHub token using AES-256-GCM + * Decrypts a secret using AES-256-GCM * - * @param encryptedToken - Base64 encoded encrypted data from encryptGitHubToken + * @param encryptedSecret - Base64 encoded encrypted data from encryptSecret * @param encryptionKey - The encryption key used for encryption - * @returns The decrypted GitHub access token + * @returns The decrypted secret * @throws Error if decryption fails or inputs are invalid */ -export function decryptGitHubToken( - encryptedToken: string, +export function decryptSecret( + encryptedSecret: string, encryptionKey: string, ): string { - if (!encryptedToken || typeof encryptedToken !== "string") { - throw new Error("Encrypted token must be a non-empty string"); + if (!encryptedSecret || typeof encryptedSecret !== "string") { + throw new Error("Encrypted secret must be a non-empty string"); } if (!encryptionKey || typeof encryptionKey !== "string") { @@ -91,11 +88,13 @@ export function decryptGitHubToken( try { // Decode the combined data - const combined = Buffer.from(encryptedToken, "base64"); + const combined = Buffer.from(encryptedSecret, "base64"); // Minimum length: IV_LENGTH + TAG_LENGTH + 1 byte for data if (combined.length < IV_LENGTH + TAG_LENGTH + 1) { - throw new Error("Invalid encrypted token format: too short or malformed"); + throw new Error( + "Invalid encrypted secret format: too short or malformed", + ); } // Extract IV, encrypted data, and tag @@ -126,7 +125,7 @@ export function decryptGitHubToken( return decryptedBuffer.toString("utf8"); } catch (error) { throw new Error( - `Failed to decrypt token: ${error instanceof Error ? error.message : "Unknown error"}`, + `Failed to decrypt secret: ${error instanceof Error ? error.message : "Unknown error"}`, ); } }