UNPKG

@cosmstack/blackshield

Version:

A developer-first security toolkit for React/Next.js applications

118 lines (114 loc) 3.56 kB
// src/core/env-validator.ts import { ZodError } from "zod"; // src/config/defaults.ts var DEFAULT_CONFIG = { dev: process.env.NODE_ENV === "development", envValidation: { allowedPublicVars: ["NEXT_PUBLIC_APP_URL", "NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_VERCEL_URL"], schema: void 0 }, xssProtection: { autoSanitize: true, customRules: {} }, boundaryProtection: { validateServerProps: true, customValidators: {} }, csrfProtection: { enabled: false, tokenHeader: "x-csrf-token", tokenCookie: "csrf-token", tokenExpiry: 3600 } }; var DANGEROUS_ENV_PATTERNS = [ /^NEXT_PUBLIC_.*SECRET/i, /^NEXT_PUBLIC_.*KEY/i, /^NEXT_PUBLIC_.*TOKEN/i, /^NEXT_PUBLIC_.*PASSWORD/i, /^NEXT_PUBLIC_.*PRIVATE/i, /^NEXT_PUBLIC_.*API_SECRET/i, /^NEXT_PUBLIC_.*DATABASE/i ]; // src/core/env-validator.ts function validateEnvironmentVariables(config = {}) { const errors = []; const warnings = []; const exposedVars = []; const publicVars = Object.keys(process.env).filter((key) => key.startsWith("NEXT_PUBLIC_")); exposedVars.push(...publicVars); for (const varName of publicVars) { const isDangerous = DANGEROUS_ENV_PATTERNS.some((pattern) => pattern.test(varName)); if (isDangerous) { const isAllowed = config.allowedPublicVars?.includes(varName); if (!isAllowed) { errors.push( `Potentially sensitive environment variable exposed: ${varName}. Consider moving to server-side only or add to allowedPublicVars if intentional.` ); } } } if (config.allowedPublicVars) { for (const varName of publicVars) { if (!config.allowedPublicVars.includes(varName)) { warnings.push( `Environment variable ${varName} is not in allowedPublicVars list. Consider adding it explicitly or removing NEXT_PUBLIC_ prefix.` ); } } } if (config.schema) { try { const envObject = Object.fromEntries(publicVars.map((key) => [key, process.env[key]])); config.schema.parse(envObject); } catch (error) { if (error instanceof ZodError) { errors.push(`Environment schema validation failed: ${error.message}`); } else { errors.push( `Environment schema validation failed: ${error instanceof Error ? error.message : "Unknown error"}` ); } } } return { isValid: errors.length === 0, errors, warnings, exposedVars }; } // src/build/next-plugin.ts function withBlackshield(nextConfig = {}, options = {}) { return { ...nextConfig, webpack: (config, context) => { if (context.isServer && context.buildId) { const validation = validateEnvironmentVariables(options.config?.envValidation); if (!validation.isValid) { console.error("\n\u{1F6E1}\uFE0F Blackshield: Environment validation failed!"); validation.errors.forEach((error) => { console.error(`\u274C ${error}`); }); if (options.failOnEnvErrors) { throw new Error("Build failed due to environment security issues"); } } if (validation.warnings.length > 0) { console.warn("\n\u{1F6E1}\uFE0F Blackshield: Environment warnings:"); validation.warnings.forEach((warning) => { console.warn(`\u26A0\uFE0F ${warning}`); }); } } if (typeof nextConfig.webpack === "function") { return nextConfig.webpack(config, context); } return config; } }; } export { withBlackshield };