diff --git a/convex/convex/githubApp.ts b/convex/convex/githubApp.ts index 01d619d98..0e91a9c9e 100644 --- a/convex/convex/githubApp.ts +++ b/convex/convex/githubApp.ts @@ -33,7 +33,7 @@ function base64UrlEncodeString(str: string): string { const REQUIRED_ENV_VARS = [ "GITHUB_APP_ID", - "GITHUB_APP_PRIVATE_KEY", + "GITHUB_APP_PRIVATE_KEY_BASE64", "GITHUB_APP_CLIENT_ID", "GITHUB_APP_CLIENT_SECRET", ] as const; @@ -47,9 +47,13 @@ export function requireGitHubAppEnv(): GitHubAppEnv { if (missing.length > 0) { throw new Error(`Missing GitHub App configuration: ${missing.join(", ")}`); } + + // Decode the Base64 string back into the strict multiline PEM format required by OpenSSL + const formattedPrivateKey = atob(process.env.GITHUB_APP_PRIVATE_KEY_BASE64!); + return { appId: process.env.GITHUB_APP_ID!, - privateKey: process.env.GITHUB_APP_PRIVATE_KEY!, + privateKey: formattedPrivateKey, clientId: process.env.GITHUB_APP_CLIENT_ID!, clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!, }; @@ -79,9 +83,8 @@ export async function generateAppJwt( const signingInput = `${header}.${payload}`; - // Normalize PEM: replace literal \n with real newlines, strip headers, decode - const normalizedPem = privateKeyPem.replace(/\\n/g, "\n"); - const pemBody = normalizedPem + // Strip PEM headers and whitespace to get raw base64 DER + const pemBody = privateKeyPem .replace(/-----BEGIN (?:RSA )?PRIVATE KEY-----/g, "") .replace(/-----END (?:RSA )?PRIVATE KEY-----/g, "") .replace(/\s/g, ""); diff --git a/convex/convex/githubAppNode.ts b/convex/convex/githubAppNode.ts index 9b252b33a..a1e547f37 100644 --- a/convex/convex/githubAppNode.ts +++ b/convex/convex/githubAppNode.ts @@ -23,7 +23,7 @@ function base64UrlEncode(data: Buffer): string { const REQUIRED_ENV_VARS = [ "GITHUB_APP_ID", - "GITHUB_APP_PRIVATE_KEY", + "GITHUB_APP_PRIVATE_KEY_BASE64", "GITHUB_APP_CLIENT_ID", "GITHUB_APP_CLIENT_SECRET", ] as const; @@ -33,9 +33,14 @@ export function requireGitHubAppEnv(): GitHubAppEnv { if (missing.length > 0) { throw new Error(`Missing GitHub App configuration: ${missing.join(", ")}`); } + + const base64Key = process.env.GITHUB_APP_PRIVATE_KEY_BASE64!; + // Decode the Base64 string back into the strict multiline PEM format required by OpenSSL + const formattedPrivateKey = Buffer.from(base64Key, "base64").toString("ascii"); + return { appId: process.env.GITHUB_APP_ID!, - privateKey: process.env.GITHUB_APP_PRIVATE_KEY!, + privateKey: formattedPrivateKey, clientId: process.env.GITHUB_APP_CLIENT_ID!, clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!, }; @@ -55,13 +60,9 @@ export function generateAppJwt(appId: string, privateKeyPem: string): string { const signingInput = `${header}.${payload}`; - // Handle stringified newlines from env var storage - const rawKey = privateKeyPem; - const formattedPrivateKey = rawKey.replace(/\\n/g, "\n"); - const sign = crypto.createSign("RSA-SHA256"); sign.update(signingInput); - const signature = sign.sign(formattedPrivateKey); + const signature = sign.sign(privateKeyPem); return `${signingInput}.${base64UrlEncode(signature)}`; } diff --git a/convex/convex/http.ts b/convex/convex/http.ts index 176f27926..9386455f2 100644 --- a/convex/convex/http.ts +++ b/convex/convex/http.ts @@ -884,7 +884,7 @@ function ghBase64UrlEncodeString(str: string): string { const GH_REQUIRED_ENV_VARS = [ "GITHUB_APP_ID", - "GITHUB_APP_PRIVATE_KEY", + "GITHUB_APP_PRIVATE_KEY_BASE64", "GITHUB_APP_CLIENT_ID", "GITHUB_APP_CLIENT_SECRET", ] as const; @@ -894,9 +894,13 @@ function requireGitHubAppEnv() { if (missing.length > 0) { throw new Error(`Missing GitHub App configuration: ${missing.join(", ")}`); } + + // Decode the Base64 string back into the strict multiline PEM format required by OpenSSL + const formattedPrivateKey = atob(process.env.GITHUB_APP_PRIVATE_KEY_BASE64!); + return { appId: process.env.GITHUB_APP_ID!, - privateKey: process.env.GITHUB_APP_PRIVATE_KEY!, + privateKey: formattedPrivateKey, clientId: process.env.GITHUB_APP_CLIENT_ID!, clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!, }; @@ -908,8 +912,8 @@ async function generateAppJwt(appId: string, privateKeyPem: string): Promise { + const userId = req.query.id; + const query = "SELECT * FROM users WHERE id = " + userId; + db.query(query, (err, results) => { + res.json(results); + }); +}); + +// Intentional XSS vulnerability for scan testing +app.get("/search", (req, res) => { + const term = req.query.q; + res.send("

Results for: " + term + "

"); +});