Install any skill in seconds. Free to start, no credit card required.
Get Started Free →This skill covers security hardening for serverless compute platforms including AWS Lambda, Azure Functions, and Google Cloud Functions. It addresses least privilege IAM roles, dependency vulnerability scanning, secrets management integration, input validation, function URL authentication, and runtime monitoring to protect against injection attacks, credential theft, and supply chain compromises.
.claude/skills/securing-serverless-functions/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-04 | ✗→✓ | ▲ Improved | — | — |
Do not use for container-based compute security (see securing-kubernetes-on-cloud), for API Gateway configuration (see implementing-cloud-waf-rules), or for serverless architecture design decisions.
Assign each Lambda function a dedicated IAM role with permissions scoped to only the specific resources it accesses. Never share IAM roles across functions.
bash# Create a least-privilege role for a specific Lambda function aws iam create-role \ --role-name order-processor-lambda-role \ --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole" }] }' # Attach a scoped policy (not AmazonDynamoDBFullAccess) aws iam put-role-policy \ --role-name order-processor-lambda-role \ --policy-name order-processor-policy \ --policy-document '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["dynamodb:PutItem", "dynamodb:GetItem"], "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/Orders" }, { "Effect": "Allow", "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], "Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/order-processor:*" }, { "Effect": "Allow", "Action": ["secretsmanager:GetSecretValue"], "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:order-api-key-*" } ] }'
Replace plaintext credentials in environment variables with references to secrets management services. Use Lambda extensions or SDK calls to retrieve secrets at runtime.
python# INSECURE: Hardcoded credentials in environment variable # DB_PASSWORD = os.environ['DB_PASSWORD'] # Stored as plaintext in Lambda config # SECURE: Retrieve from AWS Secrets Manager with caching import boto3 from botocore.exceptions import ClientError import json _secret_cache = {} def get_secret(secret_name): if secret_name in _secret_cache: return _secret_cache[secret_name] client = boto3.client('secretsmanager') response = client.get_secret_value(SecretId=secret_name) secret = json.loads(response['SecretString']) _secret_cache[secret_name] = secret return secret def lambda_handler(event, context): db_creds = get_secret('production/database/credentials') db_host = db_creds['host'] db_password = db_creds['password'] # Use credentials securely
bash# Enable encryption at rest for Lambda environment variables aws lambda update-function-configuration \ --function-name order-processor \ --kms-key-arn arn:aws:kms:us-east-1:123456789012:key/key-id
Integrate automated dependency scanning into the CI/CD pipeline to catch vulnerable packages before deployment.
bash# npm audit for Node.js Lambda functions cd lambda-function/ npm audit --audit-level=high npm audit fix # Snyk scanning in CI/CD pipeline snyk test --severity-threshold=high snyk monitor --project-name=order-processor-lambda # pip-audit for Python Lambda functions pip-audit -r requirements.txt --desc on --fix # Scan Lambda deployment package with Trivy trivy fs --severity HIGH,CRITICAL ./lambda-package/
yaml# GitHub Actions CI/CD security scanning name: Lambda Security Scan on: [push, pull_request] jobs: security: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install dependencies run: npm ci - name: Run npm audit run: npm audit --audit-level=high - name: Snyk vulnerability scan uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - name: Scan with Semgrep for code vulnerabilities uses: returntocorp/semgrep-action@v1 with: config: p/owasp-top-ten
Validate and sanitize all event input data to prevent injection attacks including SQL injection, command injection, and NoSQL injection through Lambda event sources.
pythonimport re import json from jsonschema import validate, ValidationError # Define expected input schema ORDER_SCHEMA = { "type": "object", "properties": { "orderId": {"type": "string", "pattern": "^[a-zA-Z0-9-]{1,36}$"}, "customerId": {"type": "string", "pattern": "^[a-zA-Z0-9]{1,20}$"}, "amount": {"type": "number", "minimum": 0.01, "maximum": 999999.99}, "currency": {"type": "string", "enum": ["USD", "EUR", "GBP"]} }, "required": ["orderId", "customerId", "amount", "currency"], "additionalProperties": False } def lambda_handler(event, context): # Validate API Gateway event body try: body = json.loads(event.get('body', '{}')) validate(instance=body, schema=ORDER_SCHEMA) except (json.JSONDecodeError, ValidationError) as e: return { 'statusCode': 400, 'body': json.dumps({'error': 'Invalid input', 'details': str(e)}) } # Safe to proceed with validated input order_id = body['orderId'] # Use parameterized queries for database operations
Secure function invocation endpoints with proper authentication. Never expose Lambda function URLs without IAM or Cognito authentication.
bash# Secure Lambda function URL with IAM auth (not NONE) aws lambda create-function-url-config \ --function-name order-processor \ --auth-type AWS_IAM \ --cors '{ "AllowOrigins": ["https://app.company.com"], "AllowMethods": ["POST"], "AllowHeaders": ["Content-Type", "Authorization"], "MaxAge": 3600 }' # API Gateway with Cognito authorizer aws apigateway create-authorizer \ --rest-api-id abc123 \ --name CognitoAuth \ --type COGNITO_USER_POOLS \ --provider-arns "arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_EXAMPLE"
Configure GuardDuty Lambda Network Activity Monitoring and CloudWatch structured logging to detect anomalous function behavior.
bash# Enable GuardDuty Lambda protection aws guardduty update-detector \ --detector-id <detector-id> \ --features '[{"Name": "LAMBDA_NETWORK_ACTIVITY_LOGS", "Status": "ENABLED"}]' # Configure Lambda to use structured logging aws lambda update-function-configuration \ --function-name order-processor \ --logging-config '{"LogFormat": "JSON", "ApplicationLogLevel": "INFO", "SystemLogLevel": "WARN"}'
| Term | Definition | |------|------------| | Cold Start | Initial function invocation that includes container provisioning, increasing latency and creating a window where cached secrets may not be available | | Event Injection | Attack where malicious input is embedded in Lambda event data from API Gateway, S3, SQS, or other event sources to exploit the function | | Execution Role | IAM role assumed by Lambda during execution, defining all cloud API permissions the function can use | | Function URL | Direct HTTPS endpoint for Lambda functions that can be configured with IAM or no authentication (NONE is insecure) | | Layer | Lambda deployment package containing shared code or dependencies that should be scanned for vulnerabilities independently | | Reserved Concurrency | Maximum number of concurrent executions for a function, useful for preventing resource exhaustion attacks | | Provisioned Concurrency | Pre-initialized function instances that reduce cold start latency and ensure secrets are cached |
Context: A Lambda function receives user input from API Gateway and constructs SQL queries by string concatenation against an RDS PostgreSQL database. An attacker injects SQL payloads through the API.
Approach:
python.django.security.injection.sql rule setPitfalls: Relying solely on WAF rules without fixing the underlying code vulnerability allows attackers to bypass with encoding tricks. Using ORM methods incorrectly (raw queries) still allows injection.
Serverless Security Assessment Report
=======================================
Account: 123456789012
Functions Assessed: 47
Assessment Date: 2025-02-23
CRITICAL FINDINGS:
[SLS-001] order-processor: SQL injection via string concatenation
Language: Python 3.12 | Runtime: Lambda
Vulnerable Code: f"SELECT * FROM orders WHERE id = '{order_id}'"
Remediation: Use parameterized queries with psycopg2
[SLS-002] payment-handler: Hardcoded Stripe API key in environment variable
Key: sk_live_XXXX... (unencrypted)
Remediation: Migrate to AWS Secrets Manager with KMS encryption
HIGH FINDINGS:
[SLS-003] 12 functions share the same IAM execution role with s3:*
[SLS-004] 8 functions have function URLs with AuthType: NONE
[SLS-005] 23 functions have dependencies with known HIGH CVEs
DEPENDENCY VULNERABILITIES:
axios@0.21.1: CVE-2023-45857 (HIGH) - 5 functions affected
jsonwebtoken@8.5.1: CVE-2022-23529 (CRITICAL) - 3 functions affected
lodash@4.17.15: CVE-2021-23337 (HIGH) - 11 functions affected
SUMMARY:
Critical: 2 | High: 5 | Medium: 12 | Low: 8
Functions with Least Privilege: 14/47 (30%)
Functions with Secrets Manager: 19/47 (40%)
Functions with Input Validation: 22/47 (47%)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.