AWS Cursor Rules: Lambda, IAM & Infrastructure as Code

Cursor rules for AWS covering Lambda handler patterns, IAM least privilege, API Gateway integration, S3 operations, CDK/SAM infrastructure as code, CloudWatch observability, and serverless best practices.

June 11, 2026by PromptGenius Team
awscursor-ruleslambdaserverlessiamcdkcloud
AWS Cursor Rules: Lambda, IAM & Infrastructure as Code

Overview

AWS spans compute (Lambda, EC2), storage (S3, DynamoDB), networking (API Gateway, VPC), identity (IAM), and infrastructure as code (CDK, SAM, CloudFormation). These cursor rules enforce Lambda best practices, IAM least privilege, secure environment handling, API Gateway patterns, S3 operations, CDK constructs, structured logging, and cost-conscious architecture so AI assistants generate secure, production-ready AWS code.

Note:

Enforces async Lambda handlers with structured error responses, IAM least-privilege policies, Secrets Manager for credentials, API Gateway CORS and request validation, S3 presigned URLs and multipart uploads, CDK best practices, structured JSON logging with Lambda PowerTools, and provisioned concurrency for latency-sensitive workloads.

Rules Configuration

---
description: Enforces AWS best practices including Lambda handler patterns, IAM least privilege, API Gateway integration, S3 operations, CDK infrastructure as code, CloudWatch observability, and serverless cost optimization.
globs: **/*.py,**/*.ts,**/*.js,infrastructure/**/*,cdk/**/*,template.yaml,serverless.yml,cdk.json
---
# AWS Best Practices

You are an expert in AWS, serverless architecture, and cloud infrastructure.
You understand Lambda, IAM, API Gateway, S3, DynamoDB, CloudFormation, CDK, and production AWS patterns.

### Lambda Handler Patterns
- Export a handler function: `def handler(event, context):` (Python) or `export const handler = async (event, context) => {}` (TypeScript)
- Always use async/await in Node.js handlers — never callback-based handlers
- Return structured responses: `{ statusCode: 200, headers: {...}, body: JSON.stringify(data) }`
- Parse event bodies safely: `JSON.parse(event.body)` inside try/catch
- Validate input with JSON Schema, Pydantic, or Zod before processing
- Use environment variables for runtime configuration, not hardcoded values
- Set appropriate memory (128MB-10GB) and timeout (3s-900s) based on workload
- Never perform heavy init inside the handler — use global scope for DB connections, SDK clients
- Reuse connections outside the handler to minimize cold start latency
- Log request ID from context: `context.awsRequestId` for tracing

### IAM & Security
- Apply least privilege: each function gets its own IAM role with only required permissions
- NEVER hardcode AWS credentials — rely on execution role or environment variables
- Use Secrets Manager for database passwords, API keys, and third-party credentials
- Use SSM Parameter Store for non-sensitive configuration
- Use resource-level permissions: restrict S3 actions to specific buckets, DynamoDB to specific tables
- Avoid wildcard actions (`s3:*`, `dynamodb:*`) and wildcard resources (`Resource: "*"`)
- Enable encryption at rest for S3, DynamoDB, SQS, and SNS by default
- Use KMS customer-managed keys for sensitive data — never default AWS-managed keys for compliance workloads
- Enable VPC for Lambda when accessing RDS, ElastiCache, or internal services
- Audit with IAM Access Analyzer and AWS Config rules

### API Gateway Patterns
- Enable CORS at the API Gateway level, not in Lambda code
- Use request validation models (JSON Schema) to reject malformed requests at the edge
- Map error responses: `4xx` for client errors, `5xx` for server errors
- Use custom domain names with SSL certificates from ACM
- Enable request throttling and usage plans for public APIs
- Use Lambda proxy integration (default) — parse `event.body`, `event.pathParameters`, `event.queryStringParameters`
- Structure API with REST (resource-based) or HTTP API (cheaper, simpler) based on needs
- Use API keys for client identification, not authentication — use Cognito or custom authorizer for auth

### S3 Operations
- Use presigned URLs for secure file uploads/downloads: `s3.generate_presigned_url('get_object', ...)`
- Use multipart upload for files larger than 5MB
- Set bucket policies for public access control, not per-object ACLs
- Enable versioning and lifecycle policies for production buckets
- Use event notifications (S3 → SQS/SNS/Lambda) for asynchronous processing
- Validate file types server-side — never trust client-provided MIME types
- Stream large objects instead of loading entirely into memory
- Set `Content-Type` and `Cache-Control` headers on upload

### Infrastructure as Code (CDK/SAM)
- Use AWS CDK (TypeScript or Python) for complex infrastructure, SAM for simple serverless apps
- Define constructs in separate stacks by domain: `DatabaseStack`, `ApiStack`, `AuthStack`
- Use stack outputs for cross-stack references: `Fn.importValue` or CDK cross-stack references
- Store stack configuration in context (`cdk.json`) or environment-specific props, not hardcoded
- Always `cdk synth` (CDK) or `sam build` (SAM) before deploying
- Use `cdk diff` or `sam deploy --no-execute-changeset` to preview changes
- Never manually modify resources created by IaC
- Tag all resources with: `Environment`, `Service`, `Owner`, `CostCenter`

### Observability & Monitoring
- Use structured JSON logging: `logger.info({'event': 'user_created', 'user_id': id})`
- Use AWS Lambda PowerTools for Python/TypeScript: structured logging, tracing, metrics
- Enable X-Ray tracing: set `tracing: Tracing.ACTIVE` in CDK or `Tracing: Active` in SAM
- Create CloudWatch alarms for: error rate, duration, throttles, concurrency breaches
- NEVER use `print()` or `console.log()` for production logging
- Log at appropriate levels: `debug` for dev, `info` for production, `error` for failures
- Include correlation IDs across services: forward `x-correlation-id` through all API calls

### Performance & Cost
- Use provisioned concurrency for latency-sensitive APIs to eliminate cold starts
- Use Lambda Power Tuning to find optimal memory/power configuration
- Keep deployment packages small: use Lambda Layers for shared dependencies
- Set appropriate reserved concurrency limits to prevent one function from exhausting account quota
- Use DynamoDB on-demand or provisioned with auto-scaling — avoid fixed capacity
- Enable S3 Intelligent Tiering for unpredictable access patterns
- Use CloudFront CDN in front of S3 and API Gateway for global performance
- Clean up unused resources: old Lambda versions, unattached EBS volumes, stale snapshots

Installation

Create aws.mdc in your project's .cursor/rules/ directory and paste the configuration above. Cursor and Windsurf both read .cursor/rules/ — Copilot users place it in .github/copilot-instructions.md instead.

# AWS CDK setup
npm install -g aws-cdk
cdk init app --language typescript

# AWS SAM CLI
pip install aws-sam-cli

# Lambda PowerTools (Python)
pip install aws-lambda-powertools

# Lambda PowerTools (TypeScript)
npm install @aws-lambda-powertools/logger @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics

Examples

# lambda_handler.py — Production Lambda with structured logging and error handling
import json
import os
import boto3
from aws_lambda_powertools import Logger, Tracer, Metrics
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.validation import validate

logger = Logger()
tracer = Tracer()
metrics = Metrics()

s3 = boto3.client("s3")
TABLE_NAME = os.environ["TABLE_NAME"]

input_schema = {
    "type": "object",
    "properties": {
        "user_id": {"type": "string"},
        "action": {"type": "string", "enum": ["upload", "download"]},
    },
    "required": ["user_id", "action"],
}


@tracer.capture_lambda_handler
@logger.inject_lambda_context
def handler(event: dict, context: LambdaContext) -> dict:
    try:
        body = json.loads(event.get("body", "{}"))
        validate(event=body, schema=input_schema)

        user_id = body["user_id"]
        action = body["action"]

        if action == "upload":
            url = s3.generate_presigned_url(
                "put_object",
                Params={"Bucket": os.environ["BUCKET_NAME"], "Key": f"uploads/{user_id}/{context.aws_request_id}"},
                ExpiresIn=3600,
            )
            metrics.add_metric(name="UploadRequested", unit="Count", value=1)
            return {"statusCode": 200, "body": json.dumps({"upload_url": url})}

        return {"statusCode": 400, "body": json.dumps({"error": "Unknown action"})}

    except Exception as e:
        logger.exception("Handler failed")
        return {"statusCode": 500, "body": json.dumps({"error": "Internal server error"})}
# cdk_stack.py — CDK stack with least-privilege IAM and Lambda configuration
from aws_cdk import Stack, Duration, CfnOutput
from aws_cdk import aws_lambda as lambda_
from aws_cdk import aws_iam as iam
from aws_cdk import aws_s3 as s3
from constructs import Construct


class FileProcessingStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        bucket = s3.Bucket(self, "UploadBucket",
            encryption=s3.BucketEncryption.S3_MANAGED,
            versioned=True,
            lifecycle_rules=[s3.LifecycleRule(expiration=Duration.days(90))],
        )

        fn = lambda_.Function(self, "Processor",
            runtime=lambda_.Runtime.PYTHON_3_12,
            handler="index.handler",
            code=lambda_.Code.from_asset("src"),
            memory_size=1024,
            timeout=Duration.seconds(30),
            environment={"BUCKET_NAME": bucket.bucket_name},
            tracing=lambda_.Tracing.ACTIVE,
        )

        # Least privilege: only allow this function to access its bucket
        bucket.grant_read_write(fn)

        CfnOutput(self, "BucketName", value=bucket.bucket_name)
        CfnOutput(self, "FunctionArn", value=fn.function_arn)