// SECURE: AWS Lambda without command injection
const AWS = require('aws-sdk');
const crypto = require('crypto');
const zlib = require('zlib');
const { promisify } = require('util');
const s3 = new AWS.S3();
const gzip = promisify(zlib.gzip);
// SECURE: S3 event handler using AWS SDK
exports.s3Handler = async (event, context) => {
try {
// Validate event structure
if (!event.Records || !event.Records[0] || !event.Records[0].s3) {
throw new Error('Invalid S3 event structure');
}
const record = event.Records[0];
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));
const size = record.s3.object.size;
// Validate bucket and key
if (!isValidBucket(bucket)) {
throw new Error('Bucket not allowed');
}
if (!isValidS3Key(key)) {
throw new Error('Invalid S3 key format');
}
// Size limit check
if (size > 10 * 1024 * 1024) { // 10MB
throw new Error('File too large');
}
// SECURE: Use AWS SDK instead of shell commands
const object = await s3.getObject({
Bucket: bucket,
Key: key
}).promise();
// Process file using native Node.js
const analysis = await analyzeFileNatively(key, object.Body);
// Store analysis results
const resultKey = `analysis/${key}.json`;
await s3.putObject({
Bucket: bucket,
Key: resultKey,
Body: JSON.stringify(analysis, null, 2),
ContentType: 'application/json'
}).promise();
return {
statusCode: 200,
body: JSON.stringify({
message: 'File analyzed successfully',
bucket,
originalKey: key,
resultKey,
analysis
})
};
} catch (error) {
console.error('S3 processing error:', error);
return {
statusCode: 500,
body: JSON.stringify({
error: 'Processing failed',
requestId: context.awsRequestId
})
};
}
};
// SECURE: API Gateway handler with validation
exports.apiHandler = async (event, context) => {
try {
// Validate HTTP method
if (event.httpMethod !== 'POST') {
return {
statusCode: 405,
body: JSON.stringify({ error: 'Method not allowed' })
};
}
// Parse and validate request
const body = event.body ? JSON.parse(event.body) : {};
const operation = body.operation;
const data = body.data;
// Validate operation
const allowedOperations = {
'analyze': analyzeTextData,
'hash': calculateHashData,
'compress': compressData,
'validate': validateData
};
if (!allowedOperations[operation]) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Operation not allowed' })
};
}
// Validate data
if (typeof data !== 'string' || data.length > 100000) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Invalid data format or size' })
};
}
// Execute safe operation
const result = await allowedOperations[operation](data);
return {
statusCode: 200,
body: JSON.stringify({
operation,
result,
requestId: context.awsRequestId
})
};
} catch (error) {
console.error('API processing error:', error);
return {
statusCode: 500,
body: JSON.stringify({
error: 'Processing failed',
requestId: context.awsRequestId
})
};
}
};
// SECURE: SQS handler with message validation
exports.sqsHandler = async (event, context) => {
const results = [];
for (const record of event.Records) {
try {
// Validate SQS record
if (!record.body) {
throw new Error('Empty SQS message body');
}
const message = JSON.parse(record.body);
// Validate message structure
if (!message.operation || !message.data) {
throw new Error('Invalid message format');
}
// Process message safely
const result = await processMessage(message);
results.push({ messageId: record.messageId, success: true, result });
} catch (error) {
console.error('SQS message processing error:', error);
results.push({
messageId: record.messageId,
success: false,
error: error.message
});
}
}
return {
statusCode: 200,
body: JSON.stringify({ results })
};
};
// Helper functions for secure processing
async function analyzeFileNatively(filename, fileBuffer) {
const content = fileBuffer.toString('utf8');
// File type detection based on extension
const extension = filename.toLowerCase().substring(filename.lastIndexOf('.'));
const allowedTypes = ['.txt', '.csv', '.json', '.xml', '.log'];
if (!allowedTypes.includes(extension)) {
throw new Error('File type not supported');
}
// Basic analysis using native JavaScript
const lines = content.split('\n');
const words = content.split(/\s+/).filter(word => word.length > 0);
const analysis = {
filename,
extension,
size: fileBuffer.length,
lines: lines.length,
words: words.length,
characters: content.length,
hash: crypto.createHash('sha256').update(fileBuffer).digest('hex'),
processedAt: new Date().toISOString()
};
// Additional analysis for specific file types
if (extension === '.json') {
try {
const parsed = JSON.parse(content);
analysis.jsonValid = true;
analysis.jsonKeys = Object.keys(parsed).length;
} catch {
analysis.jsonValid = false;
}
}
return analysis;
}
async function analyzeTextData(data) {
const lines = data.split('\n');
const words = data.split(/\s+/).filter(w => w.length > 0);
return {
lines: lines.length,
words: words.length,
characters: data.length,
averageLineLength: lines.reduce((sum, line) => sum + line.length, 0) / lines.length,
averageWordLength: words.reduce((sum, word) => sum + word.length, 0) / words.length
};
}
async function calculateHashData(data) {
return {
md5: crypto.createHash('md5').update(data).digest('hex'),
sha1: crypto.createHash('sha1').update(data).digest('hex'),
sha256: crypto.createHash('sha256').update(data).digest('hex')
};
}
async function compressData(data) {
const compressed = await gzip(data);
const compressionRatio = compressed.length / data.length;
return {
originalSize: data.length,
compressedSize: compressed.length,
compressionRatio: compressionRatio.toFixed(2),
savings: `${((1 - compressionRatio) * 100).toFixed(1)}%`
};
}
async function validateData(data) {
const validation = {
isValidJson: isValidJson(data),
isValidEmail: isValidEmail(data),
isValidUrl: isValidUrl(data),
containsHtml: /<[^>]*>/.test(data),
containsScripts: /<script|javascript:/i.test(data)
};
return validation;
}
async function processMessage(message) {
const allowedOperations = ['analyze', 'hash', 'compress', 'validate'];
if (!allowedOperations.includes(message.operation)) {
throw new Error('Operation not allowed');
}
if (typeof message.data !== 'string' || message.data.length > 50000) {
throw new Error('Invalid data format or size');
}
// Route to appropriate handler
switch (message.operation) {
case 'analyze':
return await analyzeTextData(message.data);
case 'hash':
return await calculateHashData(message.data);
case 'compress':
return await compressData(message.data);
case 'validate':
return await validateData(message.data);
default:
throw new Error('Unknown operation');
}
}
// Validation helper functions
function isValidBucket(bucket) {
const allowedBuckets = [
'secure-file-uploads',
'data-processing-bucket',
'analysis-results'
];
return allowedBuckets.includes(bucket);
}
function isValidS3Key(key) {
return typeof key === 'string' &&
key.length > 0 &&
key.length <= 1024 &&
/^[a-zA-Z0-9._/-]+$/.test(key) &&
!key.includes('../') &&
!/[\x00-\x1f\x7f]/.test(key);
}
function isValidJson(str) {
try {
JSON.parse(str);
return true;
} catch {
return false;
}
}
function isValidEmail(str) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(str);
}
function isValidUrl(str) {
try {
const url = new URL(str);
return ['http:', 'https:'].includes(url.protocol);
} catch {
return false;
}
}