Automated Testing Frameworks
Automated Testing Frameworks
Node.js Security Header Testing Suite
const axios = require('axios');
const chalk = require('chalk');
class SecurityHeaderTester {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.results = {
passed: [],
failed: [],
warnings: []
};
}
async runAllTests() {
console.log(chalk.blue(`\nTesting security headers for: ${this.baseUrl}\n`));
await this.testEndpoint('/', 'Homepage');
await this.testEndpoint('/api', 'API Endpoint');
await this.testEndpoint('/admin', 'Admin Panel');
await this.testEndpoint('/login', 'Login Page');
this.generateReport();
}
async testEndpoint(path, description) {
console.log(chalk.yellow(`Testing ${description} (${path})...`));
try {
const response = await axios.get(this.baseUrl + path, {
validateStatus: () => true // Don't throw on any status
});
await this.validateSecurityHeaders(response.headers, path);
await this.validateCookieSecurity(response.headers['set-cookie'], path);
await this.validateCSPPolicy(response.headers['content-security-policy'], path);
} catch (error) {
this.results.failed.push({
path,
test: 'Connection',
message: error.message
});
}
}
async validateSecurityHeaders(headers, path) {
const requiredHeaders = {
'content-security-policy': {
required: true,
validator: (value) => !value.includes('unsafe-inline') || 'Contains unsafe-inline'
},
'x-content-type-options': {
required: true,
expectedValue: 'nosniff'
},
'x-frame-options': {
required: true,
expectedValues: ['DENY', 'SAMEORIGIN']
},
'strict-transport-security': {
required: true,
validator: (value) => {
const maxAge = value.match(/max-age=(\d+)/);
return maxAge && parseInt(maxAge[1]) >= 31536000 || 'Max-age less than 1 year';
}
},
'referrer-policy': {
required: true,
expectedValues: ['no-referrer', 'strict-origin-when-cross-origin', 'same-origin']
},
'permissions-policy': {
required: false,
validator: (value) => value.includes('geolocation=()') || 'Geolocation not restricted'
}
};
Object.entries(requiredHeaders).forEach(([header, config]) => {
const value = headers[header];
if (!value && config.required) {
this.results.failed.push({
path,
test: header,
message: 'Header not set'
});
} else if (value) {
if (config.expectedValue && value !== config.expectedValue) {
this.results.failed.push({
path,
test: header,
message: `Expected "${config.expectedValue}", got "${value}"`
});
} else if (config.expectedValues && !config.expectedValues.includes(value)) {
this.results.failed.push({
path,
test: header,
message: `Invalid value: "${value}"`
});
} else if (config.validator) {
const validationResult = config.validator(value);
if (validationResult !== true) {
this.results.warnings.push({
path,
test: header,
message: validationResult
});
} else {
this.results.passed.push({
path,
test: header,
value
});
}
} else {
this.results.passed.push({
path,
test: header,
value
});
}
}
});
}
async validateCookieSecurity(cookies, path) {
if (!cookies) return;
cookies.forEach(cookie => {
const hasSecure = cookie.includes('Secure');
const hasHttpOnly = cookie.includes('HttpOnly');
const hasSameSite = cookie.includes('SameSite');
const cookieName = cookie.split('=')[0];
if (!hasSecure) {
this.results.failed.push({
path,
test: `Cookie: ${cookieName}`,
message: 'Missing Secure attribute'
});
}
if (!hasHttpOnly && cookieName.toLowerCase().includes('session')) {
this.results.warnings.push({
path,
test: `Cookie: ${cookieName}`,
message: 'Session cookie missing HttpOnly'
});
}
if (!hasSameSite) {
this.results.warnings.push({
path,
test: `Cookie: ${cookieName}`,
message: 'Missing SameSite attribute'
});
}
});
}
async validateCSPPolicy(csp, path) {
if (!csp) return;
const problematicDirectives = {
'unsafe-inline': 'script-src or style-src contains unsafe-inline',
'unsafe-eval': 'script-src contains unsafe-eval',
'*': 'Wildcard source in use',
'http:': 'Allows HTTP resources'
};
Object.entries(problematicDirectives).forEach(([pattern, message]) => {
if (csp.includes(pattern)) {
this.results.warnings.push({
path,
test: 'CSP Policy',
message
});
}
});
}
generateReport() {
console.log(chalk.blue('\n========== Security Headers Test Report ==========\n'));
if (this.results.passed.length > 0) {
console.log(chalk.green(`✓ Passed Tests: ${this.results.passed.length}`));
this.results.passed.forEach(result => {
console.log(chalk.green(` ✓ ${result.path} - ${result.test}`));
});
}
if (this.results.warnings.length > 0) {
console.log(chalk.yellow(`\n⚠ Warnings: ${this.results.warnings.length}`));
this.results.warnings.forEach(result => {
console.log(chalk.yellow(` ⚠ ${result.path} - ${result.test}: ${result.message}`));
});
}
if (this.results.failed.length > 0) {
console.log(chalk.red(`\n✗ Failed Tests: ${this.results.failed.length}`));
this.results.failed.forEach(result => {
console.log(chalk.red(` ✗ ${result.path} - ${result.test}: ${result.message}`));
});
}
const score = this.calculateSecurityScore();
console.log(chalk.blue(`\nSecurity Score: ${score}/100`));
}
calculateSecurityScore() {
const total = this.results.passed.length + this.results.failed.length;
const passRate = this.results.passed.length / total;
const warningPenalty = this.results.warnings.length * 2;
return Math.max(0, Math.round(passRate * 100 - warningPenalty));
}
}
// Usage
const tester = new SecurityHeaderTester('https://example.com');
tester.runAllTests();