apx-toolkit
Version:
Automatically discover APIs and generate complete integration packages: code in 12 languages, TypeScript types, test suites, SDK packages, API documentation, mock servers, performance reports, and contract tests. Saves 2-4 weeks of work in seconds.
249 lines (219 loc) âĸ 8.43 kB
text/typescript
/**
* Security Audit
* Analyzes discovered APIs for security issues and best practices
*/
import type { DiscoveredAPI } from '../types.js';
export interface SecurityIssue {
severity: 'critical' | 'high' | 'medium' | 'low' | 'info';
type: string;
message: string;
api?: string;
recommendation?: string;
}
export interface SecurityAuditReport {
timestamp: string;
apisAnalyzed: number;
issues: SecurityIssue[];
summary: {
critical: number;
high: number;
medium: number;
low: number;
info: number;
};
score: number; // 0-100
recommendations: string[];
}
/**
* Perform security audit on discovered APIs
*/
export function auditAPIs(apis: DiscoveredAPI[]): SecurityAuditReport {
const issues: SecurityIssue[] = [];
const recommendations: string[] = [];
apis.forEach(api => {
// Check for HTTP (not HTTPS)
if (api.url.startsWith('http://')) {
issues.push({
severity: 'high',
type: 'insecure-protocol',
message: 'API uses HTTP instead of HTTPS',
api: api.url,
recommendation: 'Use HTTPS to encrypt data in transit',
});
}
// Check for exposed API keys in URLs
if (api.url.includes('api_key=') || api.url.includes('apikey=') || api.url.includes('key=')) {
issues.push({
severity: 'critical',
type: 'exposed-credentials',
message: 'API key found in URL (visible in logs, browser history)',
api: api.url,
recommendation: 'Use headers or request body for API keys, never in URLs',
});
}
// Check for authentication
const hasAuth = api.headers['Authorization'] || api.headers['X-API-Key'] || api.headers['authorization'];
if (!hasAuth && api.method === 'POST') {
issues.push({
severity: 'medium',
type: 'missing-authentication',
message: 'POST endpoint without authentication',
api: api.url,
recommendation: 'Implement authentication for write operations',
});
}
// Check for rate limiting
if (!api.rateLimitInfo) {
issues.push({
severity: 'info',
type: 'no-rate-limit-info',
message: 'No rate limit information detected',
api: api.url,
recommendation: 'Monitor API responses for rate limit headers',
});
}
// Check for sensitive data in URLs
const sensitivePatterns = ['password', 'token', 'secret', 'key', 'auth'];
const urlLower = api.url.toLowerCase();
sensitivePatterns.forEach(pattern => {
if (urlLower.includes(pattern) && urlLower.includes('=')) {
issues.push({
severity: 'high',
type: 'sensitive-data-in-url',
message: `Potential sensitive data in URL: ${pattern}`,
api: api.url,
recommendation: 'Move sensitive parameters to headers or request body',
});
}
});
// Check for CORS headers
if (!api.headers['Access-Control-Allow-Origin']) {
issues.push({
severity: 'info',
type: 'cors-not-configured',
message: 'No CORS headers detected',
api: api.url,
recommendation: 'Configure CORS appropriately for your use case',
});
}
// Check for versioning
if (!api.url.match(/\/v\d+\//) && !api.url.match(/\/api\/v\d+\//)) {
issues.push({
severity: 'low',
type: 'no-versioning',
message: 'API endpoint does not appear to be versioned',
api: api.url,
recommendation: 'Consider API versioning for future compatibility',
});
}
});
// Calculate summary
const summary = {
critical: issues.filter(i => i.severity === 'critical').length,
high: issues.filter(i => i.severity === 'high').length,
medium: issues.filter(i => i.severity === 'medium').length,
low: issues.filter(i => i.severity === 'low').length,
info: issues.filter(i => i.severity === 'info').length,
};
// Calculate security score (0-100)
const score = calculateSecurityScore(summary, apis.length);
// Generate recommendations
if (summary.critical > 0) {
recommendations.push('Address critical security issues immediately');
}
if (summary.high > 0) {
recommendations.push('Review and fix high-severity issues');
}
if (issues.some(i => i.type === 'insecure-protocol')) {
recommendations.push('Migrate all APIs to HTTPS');
}
if (issues.some(i => i.type === 'exposed-credentials')) {
recommendations.push('Remove API keys from URLs and use secure headers');
}
if (summary.medium > 0) {
recommendations.push('Consider implementing authentication for write operations');
}
return {
timestamp: new Date().toISOString(),
apisAnalyzed: apis.length,
issues,
summary,
score,
recommendations,
};
}
/**
* Calculate security score (0-100)
*/
function calculateSecurityScore(summary: SecurityAuditReport['summary'], apiCount: number): number {
if (apiCount === 0) return 100;
let score = 100;
// Deduct points for issues
score -= summary.critical * 20; // -20 per critical
score -= summary.high * 10; // -10 per high
score -= summary.medium * 5; // -5 per medium
score -= summary.low * 2; // -2 per low
score -= summary.info * 0.5; // -0.5 per info
// Ensure score is between 0 and 100
return Math.max(0, Math.min(100, Math.round(score)));
}
/**
* Format security report as markdown
*/
export function formatSecurityReport(report: SecurityAuditReport): string {
const lines: string[] = [];
lines.push('# Security Audit Report');
lines.push('');
lines.push(`**Date:** ${new Date(report.timestamp).toLocaleString()}`);
lines.push(`**APIs Analyzed:** ${report.apisAnalyzed}`);
lines.push(`**Security Score:** ${report.score}/100`);
lines.push('');
// Summary
lines.push('## Summary');
lines.push('');
lines.push('| Severity | Count |');
lines.push('|----------|-------|');
lines.push(`| đ´ Critical | ${report.summary.critical} |`);
lines.push(`| đ High | ${report.summary.high} |`);
lines.push(`| đĄ Medium | ${report.summary.medium} |`);
lines.push(`| đĸ Low | ${report.summary.low} |`);
lines.push(`| âšī¸ Info | ${report.summary.info} |`);
lines.push('');
// Issues by severity
const severityOrder: SecurityIssue['severity'][] = ['critical', 'high', 'medium', 'low', 'info'];
severityOrder.forEach(severity => {
const severityIssues = report.issues.filter(i => i.severity === severity);
if (severityIssues.length === 0) return;
const emoji = {
critical: 'đ´',
high: 'đ ',
medium: 'đĄ',
low: 'đĸ',
info: 'âšī¸',
}[severity];
lines.push(`## ${emoji} ${severity.toUpperCase()} Issues (${severityIssues.length})`);
lines.push('');
severityIssues.forEach((issue, index) => {
lines.push(`### ${index + 1}. ${issue.type}`);
lines.push('');
lines.push(`**Message:** ${issue.message}`);
if (issue.api) {
lines.push(`**API:** \`${issue.api}\``);
}
if (issue.recommendation) {
lines.push(`**Recommendation:** ${issue.recommendation}`);
}
lines.push('');
});
});
// Recommendations
if (report.recommendations.length > 0) {
lines.push('## Recommendations');
lines.push('');
report.recommendations.forEach((rec, index) => {
lines.push(`${index + 1}. ${rec}`);
});
lines.push('');
}
return lines.join('\n');
}