From df944bb1e5672c329bce76a63482af6d6be07b76 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Fri, 25 Oct 2024 09:43:10 +0200 Subject: [PATCH] Scripts across various package encapsulated into a package named "scripts". Added a script to verify no secrets were leaked into the Git .env files (#74) --- tooling/license/package.json | 8 -- tooling/scripts/package.json | 9 ++ tooling/scripts/src/checks.mjs | 111 ++++++++++++++++++ tooling/scripts/src/dev.mjs | 4 + .../src/index.mjs => scripts/src/license.mjs} | 0 .../{version => scripts}/src/migrations.mjs | 4 +- .../src/index.mjs => scripts/src/version.mjs} | 0 tooling/version/package.json | 8 -- 8 files changed, 126 insertions(+), 18 deletions(-) delete mode 100644 tooling/license/package.json create mode 100644 tooling/scripts/package.json create mode 100644 tooling/scripts/src/checks.mjs create mode 100644 tooling/scripts/src/dev.mjs rename tooling/{license/src/index.mjs => scripts/src/license.mjs} (100%) rename tooling/{version => scripts}/src/migrations.mjs (76%) rename tooling/{version/src/index.mjs => scripts/src/version.mjs} (100%) delete mode 100644 tooling/version/package.json diff --git a/tooling/license/package.json b/tooling/license/package.json deleted file mode 100644 index 8c0780ef6..000000000 --- a/tooling/license/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "license", - "private": true, - "version": "0.1.0", - "scripts": { - "dev": "node src/index.mjs" - } -} diff --git a/tooling/scripts/package.json b/tooling/scripts/package.json new file mode 100644 index 000000000..86e9f3454 --- /dev/null +++ b/tooling/scripts/package.json @@ -0,0 +1,9 @@ +{ + "name": "scripts", + "private": true, + "version": "0.1.0", + "scripts": { + "dev": "node ./src/dev.mjs", + "checks": "node ./src/checks.mjs" + } +} diff --git a/tooling/scripts/src/checks.mjs b/tooling/scripts/src/checks.mjs new file mode 100644 index 000000000..07480731e --- /dev/null +++ b/tooling/scripts/src/checks.mjs @@ -0,0 +1,111 @@ +import { readFileSync, readdirSync } from 'fs'; +import * as path from 'path'; + +const whitelist = { + STRIPE_SECRET_KEY: [/sk_test_*/], + STRIPE_WEBHOOK_SECRET: [/whsec_*/], + EMAIL_PASSWORD: ['password'], + SUPABASE_DB_WEBHOOK_SECRET: ['WEBHOOKSECRET'], + SUPABASE_SERVICE_ROLE_KEY: ['eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'], +}; + +// List of sensitive environment variables that should not be in .env files +const sensitiveEnvVars = [ + 'STRIPE_SECRET_KEY', + 'STRIPE_WEBHOOK_SECRET', + 'LEMON_SQUEEZY_SECRET_KEY', + 'LEMON_SQUEEZY_SIGNING_SECRET', + 'KEYSTATIC_GITHUB_TOKEN', + 'SUPABASE_DB_WEBHOOK_SECRET', + 'SUPABASE_SERVICE_ROLE_KEY', + 'EMAIL_PASSWORD', + 'CAPTCHA_SECRET_TOKEN', +]; + +// Files to check +const envFiles = ['.env', '.env.development', '.env.production']; + +function checkEnvFiles(rootPath) { + let hasSecrets = false; + + envFiles.forEach((file) => { + try { + const envPath = path.join(process.cwd(), rootPath, file); + const contents = readFileSync(envPath, 'utf8'); + const lines = contents.split('\n'); + + lines.forEach((line, index) => { + // Skip empty lines and comments + if (!line || line.startsWith('#')) return; + + // Check if line contains any sensitive vars + sensitiveEnvVars.forEach((secret) => { + if (line.startsWith(`${secret}=`)) { + // Extract the value + const value = line.split('=')[1].trim().replace(/["']/g, ''); + + // Skip if value is whitelisted + if (isValueWhitelisted(secret, value)) { + return; + } + + console.error(`⚠️ Secret key "${secret}" found in ${file} on line ${index + 1}`); + + hasSecrets = true; + } + }); + }); + } catch (err) { + // File doesn't exist, skip + if (err.code === 'ENOENT') return; + + throw err; + } + }); + + if (hasSecrets) { + console.error('\n❌ Error: Secret keys found in environment files'); + + console.error( + '\nPlease remove sensitive information from .env files and store them securely:', + ); + + console.error('- Use environment variables in your CI/CD system'); + console.error('- For local development, use .env.local (git ignored)'); + process.exit(1); + } else { + const appName = rootPath.split('/').pop(); + + console.log(`✅ No secret keys found in staged environment files for the app ${appName}`); + } +} + +const apps = readdirSync('../../apps'); + +apps.forEach(app => { + checkEnvFiles(`../../apps/${app}`); +}); + +function isValueWhitelisted(key, value) { + if (!(key in whitelist)) { + return false; + } + + const whiteListedValue = whitelist[key]; + + if (whiteListedValue instanceof RegExp) { + return whiteListedValue.test(value); + } + + if (Array.isArray(whiteListedValue)) { + return whiteListedValue.some(allowed => { + if (allowed instanceof RegExp) { + return allowed.test(value); + } + + return allowed.trim() === value.trim(); + }); + } + + return whiteListedValue.trim() === value.trim(); +} \ No newline at end of file diff --git a/tooling/scripts/src/dev.mjs b/tooling/scripts/src/dev.mjs new file mode 100644 index 000000000..80daf8b37 --- /dev/null +++ b/tooling/scripts/src/dev.mjs @@ -0,0 +1,4 @@ +import { checkPendingMigrations } from './migrations.mjs'; +import './license.mjs'; + +checkPendingMigrations(); \ No newline at end of file diff --git a/tooling/license/src/index.mjs b/tooling/scripts/src/license.mjs similarity index 100% rename from tooling/license/src/index.mjs rename to tooling/scripts/src/license.mjs diff --git a/tooling/version/src/migrations.mjs b/tooling/scripts/src/migrations.mjs similarity index 76% rename from tooling/version/src/migrations.mjs rename to tooling/scripts/src/migrations.mjs index ff71748b8..5b7924d63 100644 --- a/tooling/version/src/migrations.mjs +++ b/tooling/scripts/src/migrations.mjs @@ -2,7 +2,7 @@ import { execSync } from 'node:child_process'; export function checkPendingMigrations() { try { - console.log('\nChecking for pending migrations...'); + console.info('\x1b[34m%s\x1b[0m', 'Checking for pending migrations...'); const output = execSync('pnpm --filter web supabase migration list', { encoding: 'utf-8', stdio: 'pipe' }); const lines = output.split('\n'); @@ -25,6 +25,6 @@ export function checkPendingMigrations() { console.log('\x1b[32m%s\x1b[0m', '✅ All migrations are up to date.'); } } catch (error) { - console.log('\x1b[33m%s\x1b[0m', '⚠️ No remote Supabase project found. You may not yet have linked your Supabase project. Feel free to ignore this message.'); + console.log('\x1b[33m%s\x1b[0m', '⚠️ Migrations: No remote Supabase project found, we could not check pending migrations. This is normal if you have not yet have linked your Supabase project. Feel free to ignore this message.'); } } \ No newline at end of file diff --git a/tooling/version/src/index.mjs b/tooling/scripts/src/version.mjs similarity index 100% rename from tooling/version/src/index.mjs rename to tooling/scripts/src/version.mjs diff --git a/tooling/version/package.json b/tooling/version/package.json deleted file mode 100644 index 7c4fa24ee..000000000 --- a/tooling/version/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "version", - "private": true, - "version": "0.1.0", - "scripts": { - "dev": "node src/index.mjs" - } -}