@cosmstack/blackshield
Version:
A developer-first security toolkit for React/Next.js applications
112 lines (108 loc) • 3.31 kB
JavaScript
// 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/vite-plugin.ts
function blackshieldVite(options = {}) {
return {
name: "blackshield",
buildStart() {
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}`);
});
}
}
};
}
export {
blackshieldVite
};