ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
256 lines • 9.14 kB
JavaScript
;
/**
* ctrl.shift.left QA Validation Middleware
*
* This middleware provides input validation and security checks for API routes
* in various JavaScript frameworks including Next.js, Express, and others.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateInput = validateInput;
exports.validateRequestBody = validateRequestBody;
exports.createValidationMiddleware = createValidationMiddleware;
exports.validateUrl = validateUrl;
exports.validateEmail = validateEmail;
exports.validatePassword = validatePassword;
exports.validateJson = validateJson;
/**
* Validate user input for common security risks
* @param input User input to validate
* @param inputType Type of input
*/
function validateInput(input, inputType = 'text') {
// Basic validation patterns
const patterns = {
url: /^(https?:\/\/)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?([\/\w\.-]*)*\/?$/i,
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
password: /.{8,}/, // At least 8 characters
number: /^-?\d+(\.\d+)?$/,
};
// Check for common security issues
const securityChecks = {
url: [
{ pattern: /<|>|script|on\w+=/i, message: "URL contains potentially unsafe characters" },
{ pattern: /javascript:/i, message: "URL contains JavaScript protocol" },
{ pattern: /data:/i, message: "URL contains data protocol" },
],
email: [
{ pattern: /<|>|script|on\w+=/i, message: "Email contains potentially unsafe characters" },
],
password: [
{ pattern: /^(password|123456|admin|qwerty)/i, message: "Password is too common" },
],
text: [
{ pattern: /<script|javascript:|on\w+=/i, message: "Text contains potentially unsafe code" },
{ pattern: /\bdrop\s+table|\bdelete\s+from|\bupdate\s+\w+\s+set/i, message: "Text contains potential SQL commands" },
],
json: [
{ pattern: /<script|javascript:|on\w+=/i, message: "JSON contains potentially unsafe code" },
{ pattern: /__proto__|constructor|prototype/i, message: "JSON contains potential prototype pollution" }
],
number: [
{ pattern: /[^\d.-]/i, message: "Number contains non-numeric characters" }
]
};
// Results
const result = {
valid: false,
message: '',
securityIssues: [],
};
// Handle empty input
if (input === undefined || input === null || input === '') {
result.message = 'Input is empty';
result.securityIssues.push('Empty input received');
return result;
}
// Convert to string if not already
const inputStr = String(input);
// Check basic pattern if available
if (patterns[inputType] && !patterns[inputType].test(inputStr)) {
result.message = `Invalid ${inputType} format`;
result.securityIssues.push(result.message);
return result;
}
// Check security issues
if (securityChecks[inputType]) {
for (const check of securityChecks[inputType]) {
if (check.pattern.test(inputStr)) {
result.securityIssues.push(check.message);
}
}
}
result.valid = result.securityIssues.length === 0;
result.message = result.securityIssues.length > 0
? result.securityIssues[0]
: `Valid ${inputType}`;
return result;
}
/**
* Validate request body fields
* @param body Request body object
* @param fieldValidations Validation rules for each field
*/
function validateRequestBody(body, fieldValidations) {
const result = {
isValid: true,
errors: {},
securityIssues: []
};
for (const [field, validation] of Object.entries(fieldValidations)) {
const isRequired = validation.required !== false; // Default to true
// Check if required field is missing
if (isRequired && (body[field] === undefined || body[field] === null || body[field] === '')) {
result.isValid = false;
result.errors[field] = `${field} is required`;
continue;
}
// Skip validation for optional empty fields
if (!isRequired && (body[field] === undefined || body[field] === null || body[field] === '')) {
continue;
}
// Validate the field
const validationResult = validateInput(body[field], validation.type);
if (!validationResult.valid) {
result.isValid = false;
result.errors[field] = validationResult.message;
// Add security issues with field context
validationResult.securityIssues.forEach(issue => {
result.securityIssues.push(`${field}: ${issue}`);
});
}
}
return result;
}
/**
* Create Express-style middleware for input validation
* @param validationRules Validation rules for request body fields
*/
function createValidationMiddleware(validationRules) {
return function validationMiddleware(req, res, next) {
const validationResult = validateRequestBody(req.body, validationRules);
if (!validationResult.isValid) {
return res.status(400).json({
error: 'Validation failed',
details: validationResult.errors,
securityIssues: validationResult.securityIssues
});
}
// Add validation result to request for further processing if needed
req.validationResult = validationResult;
next();
};
}
/**
* Validate URL with security checks
* @param url URL to validate
*/
function validateUrl(url) {
const result = validateInput(url, 'url');
return {
isValid: result.valid,
errorMessage: !result.valid ? result.message : '',
hasSecurity: result.securityIssues?.length > 0,
securityIssues: result.securityIssues || []
};
}
/**
* Validate email address with security checks
* @param email Email to validate
*/
function validateEmail(email) {
const result = validateInput(email, 'email');
return {
isValid: result.valid,
errorMessage: !result.valid ? result.message : '',
hasSecurity: result.securityIssues?.length > 0,
securityIssues: result.securityIssues || []
};
}
/**
* Validate a password for security and strength
* @param password Password to validate
*/
function validatePassword(password) {
const result = validateInput(password, 'password');
// Additional password strength checks
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
const strength = [hasUppercase, hasLowercase, hasNumber, hasSpecial]
.filter(Boolean).length;
let strengthLabel = 'weak';
if (strength >= 3 && password.length >= 10) {
strengthLabel = 'strong';
}
else if (strength >= 2 && password.length >= 8) {
strengthLabel = 'medium';
}
return {
isValid: result.valid,
errorMessage: !result.valid ? result.message : '',
hasSecurity: result.securityIssues?.length > 0,
securityIssues: result.securityIssues || [],
strength: strengthLabel,
suggestions: strength < 3 ? [
!hasUppercase ? 'Add uppercase letters' : null,
!hasLowercase ? 'Add lowercase letters' : null,
!hasNumber ? 'Add numbers' : null,
!hasSpecial ? 'Add special characters' : null,
password.length < 10 ? 'Make it at least 10 characters long' : null
].filter(Boolean) : []
};
}
/**
* Validate JSON string or object for security issues
* @param json JSON string or object to validate
*/
function validateJson(json) {
let jsonString;
if (typeof json === 'object') {
try {
jsonString = JSON.stringify(json);
}
catch (error) {
return {
isValid: false,
errorMessage: 'Invalid JSON object',
hasSecurity: false,
securityIssues: []
};
}
}
else {
jsonString = json;
// Try to parse to verify it's valid JSON
try {
JSON.parse(jsonString);
}
catch (error) {
return {
isValid: false,
errorMessage: 'Invalid JSON string',
hasSecurity: false,
securityIssues: []
};
}
}
const result = validateInput(jsonString, 'json');
return {
isValid: result.valid,
errorMessage: !result.valid ? result.message : '',
hasSecurity: result.securityIssues?.length > 0,
securityIssues: result.securityIssues || []
};
}
// Export all validation utilities
exports.default = {
validateInput,
validateRequestBody,
createValidationMiddleware,
validateUrl,
validateEmail,
validatePassword,
validateJson
};
//# sourceMappingURL=qa-validation.js.map