@vptech/aws-security-baseline
Version:
Auto-generate AWS security baselines, IAM policies, and security groups from TypeScript interfaces
399 lines • 16.2 kB
JavaScript
"use strict";
/**
* TypeScript Security Analyzer
* Analyzes TypeScript interfaces to determine security requirements
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeScriptSecurityAnalyzer = void 0;
const ts = __importStar(require("typescript"));
const path = __importStar(require("path"));
class TypeScriptSecurityAnalyzer {
/**
* Analyze a TypeScript interface for security requirements
*/
analyzeInterface(filePath, interfaceName) {
const program = this.createProgram(filePath);
const sourceFile = program.getSourceFile(filePath);
if (!sourceFile) {
throw new Error(`Source file not found: ${filePath}`);
}
const interfaceDeclaration = this.findInterface(sourceFile, interfaceName);
if (!interfaceDeclaration) {
throw new Error(`Interface '${interfaceName}' not found in ${filePath}`);
}
const resourceAccess = this.analyzeResourceAccess(interfaceDeclaration);
const networkRequirements = this.analyzeNetworkRequirements(interfaceDeclaration);
const dataClassification = this.analyzeDataClassification(interfaceDeclaration);
const recommendedPolicies = this.generateRecommendedPolicies(resourceAccess, dataClassification);
return {
interfaceName,
filePath,
resourceAccess,
networkRequirements,
dataClassification,
recommendedPolicies
};
}
createProgram(filePath) {
const configPath = ts.findConfigFile(path.dirname(filePath), ts.sys.fileExists, 'tsconfig.json');
let compilerOptions = {
target: ts.ScriptTarget.ES2020,
module: ts.ModuleKind.CommonJS,
strict: true
};
if (configPath) {
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath));
compilerOptions = parsedConfig.options;
}
return ts.createProgram([filePath], compilerOptions);
}
findInterface(sourceFile, interfaceName) {
let interfaceDecl;
const visit = (node) => {
if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName) {
interfaceDecl = node;
return;
}
ts.forEachChild(node, visit);
};
visit(sourceFile);
return interfaceDecl;
}
analyzeResourceAccess(interfaceDecl) {
const resourceAccess = [];
const checker = this.createProgram(interfaceDecl.getSourceFile().fileName).getTypeChecker();
interfaceDecl.members.forEach(member => {
if (ts.isPropertySignature(member) && member.name) {
const propertyName = member.name.getText();
const jsDocTags = this.extractJSDocTags(member);
// Analyze property for resource access patterns
const access = this.determineResourceAccess(propertyName, member, jsDocTags);
if (access) {
resourceAccess.push(access);
}
}
});
return resourceAccess;
}
analyzeNetworkRequirements(interfaceDecl) {
const networkAccess = [];
interfaceDecl.members.forEach(member => {
if (ts.isPropertySignature(member) && member.name) {
const propertyName = member.name.getText();
const jsDocTags = this.extractJSDocTags(member);
// Check for network-related properties
if (this.isNetworkProperty(propertyName, jsDocTags)) {
const access = this.extractNetworkRequirements(propertyName, jsDocTags);
if (access) {
networkAccess.push(access);
}
}
}
});
return networkAccess;
}
analyzeDataClassification(interfaceDecl) {
const categories = [];
let highestLevel = 'public';
let encryptionRequired = false;
interfaceDecl.members.forEach(member => {
if (ts.isPropertySignature(member) && member.name) {
const propertyName = member.name.getText();
const jsDocTags = this.extractJSDocTags(member);
// Classify data based on property names and annotations
const classification = this.classifyProperty(propertyName, jsDocTags);
if (classification) {
categories.push(classification);
// Update highest security level
if (this.getSecurityLevel(classification.type) > this.getSecurityLevel(highestLevel)) {
highestLevel = this.mapDataTypeToSecurityLevel(classification.type);
}
// Check if encryption is required
if (['pii', 'phi', 'financial'].includes(classification.type)) {
encryptionRequired = true;
}
}
}
});
const retentionRequirements = this.generateRetentionRequirements(categories);
return {
level: highestLevel,
categories,
retentionRequirements,
encryptionRequired
};
}
extractJSDocTags(node) {
const tags = new Map();
const jsDocTags = ts.getJSDocTags(node);
jsDocTags.forEach(tag => {
if (tag.tagName && tag.comment) {
tags.set(tag.tagName.text, typeof tag.comment === 'string' ? tag.comment : tag.comment.map(c => c.text).join(''));
}
});
return tags;
}
determineResourceAccess(propertyName, member, jsDocTags) {
// Check JSDoc annotations for explicit resource access patterns
if (jsDocTags.has('resource')) {
const resourceType = jsDocTags.get('resource');
const accessLevel = this.determineAccessLevel(jsDocTags);
return {
resourceType,
accessLevel,
encryption: {
enabled: jsDocTags.has('encrypted') || this.requiresEncryption(propertyName),
keyRotation: jsDocTags.has('keyRotation'),
algorithm: jsDocTags.get('encryption') || 'aws:kms'
}
};
}
// Infer resource access from property patterns
if (propertyName.includes('s3') || propertyName.includes('bucket')) {
return {
resourceType: 's3',
accessLevel: this.inferAccessLevel(propertyName),
encryption: {
enabled: true,
algorithm: 'aws:kms'
}
};
}
if (propertyName.includes('db') || propertyName.includes('database')) {
return {
resourceType: 'rds',
accessLevel: 'private',
encryption: {
enabled: true,
algorithm: 'aws:kms',
keyRotation: true
}
};
}
return null;
}
isNetworkProperty(propertyName, jsDocTags) {
return jsDocTags.has('port') ||
jsDocTags.has('protocol') ||
propertyName.includes('port') ||
propertyName.includes('endpoint') ||
propertyName.includes('url');
}
extractNetworkRequirements(propertyName, jsDocTags) {
if (jsDocTags.has('port')) {
const port = jsDocTags.get('port');
const protocol = jsDocTags.get('protocol') || 'tcp';
const source = jsDocTags.get('allowFrom') || '0.0.0.0/0';
return {
protocol: protocol,
port: isNaN(Number(port)) ? port : Number(port),
sourceType: source.includes('/') ? 'cidr' : 'security-group',
source,
description: `Access to ${propertyName} on port ${port}`
};
}
return null;
}
classifyProperty(propertyName, jsDocTags) {
// Check explicit JSDoc classification
if (jsDocTags.has('classification')) {
const classification = jsDocTags.get('classification');
return {
name: propertyName,
type: classification,
fields: [propertyName],
regulations: this.getRegulationsForType(classification)
};
}
// Infer classification from property names
const piiPatterns = ['email', 'phone', 'address', 'ssn', 'name', 'dob', 'birthdate'];
const phiPatterns = ['medical', 'health', 'diagnosis', 'treatment', 'patient', 'medication'];
const financialPatterns = ['payment', 'card', 'account', 'balance', 'transaction', 'billing'];
const propertyLower = propertyName.toLowerCase();
if (piiPatterns.some(pattern => propertyLower.includes(pattern))) {
return {
name: propertyName,
type: 'pii',
fields: [propertyName],
regulations: ['GDPR', 'CCPA', 'PIPEDA']
};
}
if (phiPatterns.some(pattern => propertyLower.includes(pattern))) {
return {
name: propertyName,
type: 'phi',
fields: [propertyName],
regulations: ['HIPAA', 'Privacy Act 1988 (Australia)']
};
}
if (financialPatterns.some(pattern => propertyLower.includes(pattern))) {
return {
name: propertyName,
type: 'financial',
fields: [propertyName],
regulations: ['PCI-DSS', 'SOX', 'GDPR']
};
}
return null;
}
determineAccessLevel(jsDocTags) {
if (jsDocTags.has('access')) {
return jsDocTags.get('access');
}
return 'private';
}
inferAccessLevel(propertyName) {
if (propertyName.includes('public'))
return 'public';
if (propertyName.includes('internal'))
return 'internal';
if (propertyName.includes('restricted'))
return 'restricted';
return 'private';
}
requiresEncryption(propertyName) {
const sensitivePatterns = ['password', 'secret', 'key', 'token', 'credential', 'private'];
return sensitivePatterns.some(pattern => propertyName.toLowerCase().includes(pattern));
}
getSecurityLevel(level) {
const levels = { public: 0, internal: 1, confidential: 2, restricted: 3 };
return levels[level] || 0;
}
mapDataTypeToSecurityLevel(type) {
switch (type) {
case 'phi':
case 'financial':
return 'restricted';
case 'pii':
return 'confidential';
case 'proprietary':
return 'internal';
default:
return 'public';
}
}
getRegulationsForType(type) {
switch (type) {
case 'pii':
return ['GDPR', 'CCPA', 'PIPEDA'];
case 'phi':
return ['HIPAA', 'Privacy Act 1988 (Australia)'];
case 'financial':
return ['PCI-DSS', 'SOX', 'GDPR'];
case 'proprietary':
return ['Trade Secrets Act'];
default:
return [];
}
}
generateRetentionRequirements(categories) {
const requirements = [];
categories.forEach(category => {
switch (category.type) {
case 'pii':
requirements.push({
category: category.name,
retentionPeriod: '7 years',
disposalMethod: 'Secure deletion with cryptographic erasure',
legalBasis: 'GDPR Article 17 (Right to erasure)'
});
break;
case 'phi':
requirements.push({
category: category.name,
retentionPeriod: '6 years',
disposalMethod: 'HIPAA-compliant secure deletion',
legalBasis: 'HIPAA 45 CFR 164.316(b)(2)(i)'
});
break;
case 'financial':
requirements.push({
category: category.name,
retentionPeriod: '7 years',
disposalMethod: 'Secure deletion with audit trail',
legalBasis: 'SOX Section 802'
});
break;
}
});
return requirements;
}
generateRecommendedPolicies(resourceAccess, dataClassification) {
const policies = [];
// Add policies based on data classification
if (dataClassification.level === 'restricted') {
policies.push('RestrictedDataAccessPolicy');
policies.push('EncryptionAtRestRequiredPolicy');
policies.push('EncryptionInTransitRequiredPolicy');
}
if (dataClassification.level === 'confidential') {
policies.push('ConfidentialDataAccessPolicy');
policies.push('EncryptionAtRestRequiredPolicy');
}
// Add policies based on resource access
resourceAccess.forEach(access => {
switch (access.resourceType) {
case 's3':
policies.push('S3SecureAccessPolicy');
if (access.encryption?.enabled) {
policies.push('S3EncryptionRequiredPolicy');
}
break;
case 'rds':
policies.push('RDSSecureAccessPolicy');
policies.push('DatabaseEncryptionPolicy');
break;
}
});
// Add compliance-specific policies
const hasPhiData = dataClassification.categories.some(c => c.type === 'phi');
const hasPiiData = dataClassification.categories.some(c => c.type === 'pii');
const hasFinancialData = dataClassification.categories.some(c => c.type === 'financial');
if (hasPhiData) {
policies.push('HIPAACompliancePolicy');
}
if (hasPiiData) {
policies.push('GDPRCompliancePolicy');
}
if (hasFinancialData) {
policies.push('PCIDSSCompliancePolicy');
}
return [...new Set(policies)]; // Remove duplicates
}
}
exports.TypeScriptSecurityAnalyzer = TypeScriptSecurityAnalyzer;
//# sourceMappingURL=typescript-analyzer.js.map