mirror of
https://github.com/codeflash-ai/codeflash-internal.git
synced 2026-05-04 18:25:18 +00:00
fixes CF-932 # Pull Request Checklist ## Description - [ ] **Description of PR**: Clear and concise description of what this PR accomplishes - [ ] **Breaking Changes**: Document any breaking changes (if applicable) - [ ] **Related Issues**: Link to any related issues or tickets ## Testing - [ ] **Test cases Attached**: All relevant test cases have been added/updated - [ ] **Manual Testing**: Manual testing completed for the changes ## Monitoring & Debugging - [ ] **Logging in place**: Appropriate logging has been added for debugging user issues - [ ] **Sentry will be able to catch errors**: Error handling ensures Sentry can capture and report errors - [ ] **Avoid Dev based/Prisma logging**: No development-only or Prisma-specific logging in production code ## Configuration - [ ] **Env variables newly added**: Any new environment variables are documented in .env.example file or mentioned in description --- ## Additional Notes <!-- Add any additional context, screenshots, or notes for reviewers here --> --------- Co-authored-by: codeflash-ai[bot] <148906541+codeflash-ai[bot]@users.noreply.github.com> Co-authored-by: ali <mohammed18200118@gmail.com> Co-authored-by: Kevin Turcios <106575910+KRRT7@users.noreply.github.com>
166 lines
5.2 KiB
TypeScript
166 lines
5.2 KiB
TypeScript
import sodium from "libsodium-wrappers"
|
|
import { type Octokit } from "octokit"
|
|
import * as Sentry from "@sentry/node"
|
|
|
|
/**
|
|
* Encrypts a secret value using GitHub's public key encryption (NaCl box seal)
|
|
* @param publicKey - Base64-encoded public key from GitHub
|
|
* @param secretValue - The plaintext secret value to encrypt
|
|
* @returns Object containing encrypted_value (base64) and key_id
|
|
*/
|
|
export async function encryptSecret(
|
|
publicKey: string,
|
|
secretValue: string,
|
|
): Promise<{ encrypted_value: string; key_id: string }> {
|
|
await sodium.ready
|
|
|
|
console.log(
|
|
`[secret-utils.ts:encryptSecret] Encrypting secret value (length: ${secretValue.length})`,
|
|
)
|
|
|
|
// Decode the public key from base64
|
|
const publicKeyBytes = Buffer.from(publicKey, "base64")
|
|
|
|
// Convert secret to bytes
|
|
const secretBytes = sodium.from_string(secretValue)
|
|
|
|
// Encrypt using crypto_box_seal (NaCl box seal encryption)
|
|
// This is the format GitHub expects for repository secrets
|
|
const encryptedBytes = sodium.crypto_box_seal(secretBytes, publicKeyBytes)
|
|
|
|
// Convert encrypted bytes to base64
|
|
const encryptedValue = Buffer.from(encryptedBytes).toString("base64")
|
|
|
|
console.log(
|
|
`[secret-utils.ts:encryptSecret] Successfully encrypted secret (encrypted length: ${encryptedValue.length})`,
|
|
)
|
|
|
|
// Note: key_id is returned from GitHub's public key endpoint, not generated here
|
|
// We'll extract it from the getRepositoryPublicKey response
|
|
return {
|
|
encrypted_value: encryptedValue,
|
|
key_id: "", // Will be set by caller from public key response
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the repository's public key for encrypting secrets
|
|
* @param octokit - Authenticated Octokit instance
|
|
* @param owner - Repository owner
|
|
* @param repo - Repository name
|
|
* @returns Object containing public_key (base64) and key_id
|
|
*/
|
|
export async function getRepositoryPublicKey(
|
|
octokit: Octokit,
|
|
owner: string,
|
|
repo: string,
|
|
): Promise<{ public_key: string; key_id: string }> {
|
|
try {
|
|
console.log(`[secret-utils.ts:getRepositoryPublicKey] Getting public key for ${owner}/${repo}`)
|
|
|
|
const response = await octokit.rest.actions.getRepoPublicKey({
|
|
owner,
|
|
repo,
|
|
})
|
|
|
|
console.log(
|
|
`[secret-utils.ts:getRepositoryPublicKey] Successfully retrieved public key for ${owner}/${repo}, key_id=${response.data.key_id}`,
|
|
)
|
|
|
|
return {
|
|
public_key: response.data.key,
|
|
key_id: response.data.key_id,
|
|
}
|
|
} catch (error: any) {
|
|
console.error(
|
|
`[secret-utils.ts:getRepositoryPublicKey] Error getting public key for ${owner}/${repo}:`,
|
|
error,
|
|
)
|
|
Sentry.captureException(error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates or updates a repository secret
|
|
* @param octokit - Authenticated Octokit instance
|
|
* @param owner - Repository owner
|
|
* @param repo - Repository name
|
|
* @param secretName - Name of the secret (e.g., "CODEFLASH_API_KEY")
|
|
* @param encryptedValue - Base64-encoded encrypted secret value
|
|
* @param keyId - The key ID from the repository's public key
|
|
*/
|
|
export async function createOrUpdateSecret(
|
|
octokit: Octokit,
|
|
owner: string,
|
|
repo: string,
|
|
secretName: string,
|
|
encryptedValue: string,
|
|
keyId: string,
|
|
): Promise<void> {
|
|
try {
|
|
console.log(
|
|
`[secret-utils.ts:createOrUpdateSecret] Creating/updating secret ${secretName} for ${owner}/${repo}`,
|
|
)
|
|
|
|
await octokit.rest.actions.createOrUpdateRepoSecret({
|
|
owner,
|
|
repo,
|
|
secret_name: secretName,
|
|
encrypted_value: encryptedValue,
|
|
key_id: keyId,
|
|
})
|
|
|
|
console.log(
|
|
`[secret-utils.ts:createOrUpdateSecret] Successfully created/updated secret ${secretName} for ${owner}/${repo}`,
|
|
)
|
|
} catch (error: any) {
|
|
console.error(
|
|
`[secret-utils.ts:createOrUpdateSecret] Error creating/updating secret ${secretName} for ${owner}/${repo}:`,
|
|
error,
|
|
)
|
|
Sentry.captureException(error)
|
|
|
|
// Check if it's a permission error (403)
|
|
if (error.status === 403) {
|
|
const errorMsg =
|
|
"The GitHub App does not have 'secrets: write' permission, or organization policies prevent secret creation."
|
|
throw new Error(errorMsg)
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Complete helper function to encrypt and store a secret in one call
|
|
* @param octokit - Authenticated Octokit instance
|
|
* @param owner - Repository owner
|
|
* @param repo - Repository name
|
|
* @param secretName - Name of the secret (e.g., "CODEFLASH_API_KEY")
|
|
* @param secretValue - Plaintext secret value to encrypt and store
|
|
*/
|
|
export async function encryptAndStoreSecret(
|
|
octokit: Octokit,
|
|
owner: string,
|
|
repo: string,
|
|
secretName: string,
|
|
secretValue: string,
|
|
): Promise<void> {
|
|
console.log(
|
|
`[secret-utils.ts:encryptAndStoreSecret] Starting encryption and storage of secret ${secretName} for ${owner}/${repo}`,
|
|
)
|
|
|
|
// Step 1: Get repository public key
|
|
const { public_key, key_id } = await getRepositoryPublicKey(octokit, owner, repo)
|
|
|
|
// Step 2: Encrypt the secret
|
|
const { encrypted_value } = await encryptSecret(public_key, secretValue)
|
|
|
|
// Step 3: Store the encrypted secret
|
|
await createOrUpdateSecret(octokit, owner, repo, secretName, encrypted_value, key_id)
|
|
|
|
console.log(
|
|
`[secret-utils.ts:encryptAndStoreSecret] Successfully encrypted and stored secret ${secretName} for ${owner}/${repo}`,
|
|
)
|
|
}
|