mcp-swagger-parser
Version:
Enterprise-grade OpenAPI/Swagger specification parser for Model Context Protocol (MCP) projects
209 lines • 8.59 kB
JavaScript
;
/**
* Security extractor for OpenAPI specifications
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityExtractor = void 0;
class SecurityExtractor {
/**
* Extract all security information from OpenAPI specification
*/
static extractSecurity(spec) {
const schemes = this.extractSecuritySchemes(spec);
const globalSecurity = spec.security || [];
const operationSecurity = this.extractOperationSecurity(spec);
const unsecuredOperations = this.findUnsecuredOperations(spec);
return {
schemes,
globalSecurity,
operationSecurity,
unsecuredOperations
};
}
/**
* Extract security schemes from components
*/
static extractSecuritySchemes(spec) {
const schemes = [];
if (!spec.components?.securitySchemes) {
return schemes;
}
const usageCounts = this.analyzeSecurityUsage(spec);
for (const [name, scheme] of Object.entries(spec.components.securitySchemes)) {
if (this.isReferenceObject(scheme)) {
continue; // Skip reference objects
}
const extractedScheme = {
name,
scheme,
usageCount: usageCounts[name]?.count || 0,
usedInOperations: usageCounts[name]?.operations || []
};
schemes.push(extractedScheme);
}
return schemes;
}
/**
* Analyze security scheme usage throughout the specification
*/
static analyzeSecurityUsage(spec) {
const usage = {};
// Check global security
if (spec.security) {
for (const requirement of spec.security) {
for (const schemeName of Object.keys(requirement)) {
if (!usage[schemeName]) {
usage[schemeName] = { count: 0, operations: [] };
}
usage[schemeName].count++;
}
}
}
// Check operation-level security
if (spec.paths) {
for (const [path, pathItem] of Object.entries(spec.paths)) {
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'];
for (const method of methods) {
const operation = pathItem[method];
if (!operation)
continue;
const operationId = `${method.toUpperCase()} ${path}`;
if (operation.security) {
for (const requirement of operation.security) {
for (const schemeName of Object.keys(requirement)) {
if (!usage[schemeName]) {
usage[schemeName] = { count: 0, operations: [] };
}
usage[schemeName].count++;
usage[schemeName].operations.push(operationId);
}
}
}
}
}
}
return usage;
}
/**
* Extract operation-level security requirements
*/
static extractOperationSecurity(spec) {
const operationSecurity = {};
if (!spec.paths) {
return operationSecurity;
}
for (const [path, pathItem] of Object.entries(spec.paths)) {
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'];
for (const method of methods) {
const operation = pathItem[method];
if (!operation)
continue;
const operationId = operation.operationId || `${method.toUpperCase()} ${path}`;
if (operation.security) {
operationSecurity[operationId] = operation.security;
}
else if (spec.security) {
// Use global security if no operation-specific security
operationSecurity[operationId] = spec.security;
}
}
}
return operationSecurity;
}
/**
* Find operations without security requirements
*/
static findUnsecuredOperations(spec) {
const unsecured = [];
if (!spec.paths) {
return unsecured;
}
const hasGlobalSecurity = spec.security && spec.security.length > 0;
for (const [path, pathItem] of Object.entries(spec.paths)) {
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'];
for (const method of methods) {
const operation = pathItem[method];
if (!operation)
continue;
const operationId = operation.operationId || `${method.toUpperCase()} ${path}`;
// Check if operation has security
const hasOperationSecurity = operation.security && operation.security.length > 0;
const hasEmptyOperationSecurity = operation.security && operation.security.length === 0;
if (hasEmptyOperationSecurity || (!hasOperationSecurity && !hasGlobalSecurity)) {
unsecured.push(operationId);
}
}
}
return unsecured;
}
/**
* Check if object is a reference
*/
static isReferenceObject(obj) {
return obj && typeof obj === 'object' && '$ref' in obj;
}
/**
* Get security scheme statistics
*/
static getSecurityStats(analysis) {
const stats = {
totalSchemes: analysis.schemes.length,
schemeTypes: {},
unusedSchemes: 0,
globalSecurityCount: analysis.globalSecurity.length,
unsecuredOperationsCount: analysis.unsecuredOperations.length,
mostUsedScheme: null,
leastUsedScheme: null
};
// Count scheme types
for (const scheme of analysis.schemes) {
const type = scheme.scheme.type;
stats.schemeTypes[type] = (stats.schemeTypes[type] || 0) + 1;
if (scheme.usageCount === 0) {
stats.unusedSchemes++;
}
}
// Find most and least used schemes
if (analysis.schemes.length > 0) {
const sortedByUsage = [...analysis.schemes].sort((a, b) => b.usageCount - a.usageCount);
stats.mostUsedScheme = sortedByUsage[0].name;
stats.leastUsedScheme = sortedByUsage[sortedByUsage.length - 1].name;
}
return stats;
}
/**
* Validate security configuration
*/
static validateSecurityConfig(analysis) {
const warnings = [];
const recommendations = [];
// Check for unused security schemes
const unusedSchemes = analysis.schemes.filter(scheme => scheme.usageCount === 0);
if (unusedSchemes.length > 0) {
warnings.push(`Found ${unusedSchemes.length} unused security scheme(s): ${unusedSchemes.map(s => s.name).join(', ')}`);
}
// Check for unsecured operations
if (analysis.unsecuredOperations.length > 0) {
warnings.push(`Found ${analysis.unsecuredOperations.length} unsecured operation(s)`);
recommendations.push('Consider adding security requirements to all operations');
}
// Check for weak authentication methods
const weakSchemes = analysis.schemes.filter(scheme => scheme.scheme.type === 'apiKey' && scheme.scheme.in === 'query');
if (weakSchemes.length > 0) {
recommendations.push('Avoid using API keys in query parameters; use headers or cookies instead');
}
// Check for missing OAuth scopes documentation
const oauthSchemes = analysis.schemes.filter(scheme => scheme.scheme.type === 'oauth2');
for (const scheme of oauthSchemes) {
if (scheme.scheme.flows) {
const hasEmptyScopes = Object.values(scheme.scheme.flows).some(flow => flow && Object.keys(flow.scopes || {}).length === 0);
if (hasEmptyScopes) {
recommendations.push(`OAuth2 scheme '${scheme.name}' should define scopes for better access control`);
}
}
}
return { warnings, recommendations };
}
}
exports.SecurityExtractor = SecurityExtractor;
//# sourceMappingURL=security-extractor.js.map