Serverless & Edge Computing

Serverless Security Hardening

Serverless removes server patching from your plate but introduces new attack surfaces: overly permissive IAM roles, injection through event sources, dependency vulnerabilities in Lambda packages, and exposed API endpoints. We harden your serverless application against the OWASP Serverless Top 10 with IAM least-privilege enforcement, input validation, dependency scanning in CI, WAF rules, and secrets rotation — so your application meets security audit requirements.

Need this done for your project?

We implement, you ship. Async, documented, done in days.

Start a Brief

IAM Least-Privilege Enforcement

The most critical serverless security control is ensuring every Lambda function has only the permissions it needs. We audit and restrict IAM policies to exact resource ARNs and actions.

# BAD — overly permissive (common in tutorials)
{
  "Effect": "Allow",
  "Action": "dynamodb:*",
  "Resource": "*"
}

# GOOD — scoped to exact table and actions
resource "aws_iam_role_policy" "order_handler" {
  role = aws_iam_role.order_handler.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid      = "ReadWriteOrdersTable"
        Effect   = "Allow"
        Action   = [
          "dynamodb:GetItem",
          "dynamodb:PutItem",
          "dynamodb:UpdateItem",
          "dynamodb:Query",
        ]
        Resource = [
          aws_dynamodb_table.orders.arn,
          "${aws_dynamodb_table.orders.arn}/index/*",
        ]
      },
      {
        Sid      = "PublishToOrderEvents"
        Effect   = "Allow"
        Action   = ["events:PutEvents"]
        Resource = aws_cloudwatch_event_bus.main.arn
        Condition = {
          StringEquals = {
            "events:source" = "orders"  # Can only publish as 'orders' source
          }
        }
      }
    ]
  })
}

# IAM Access Analyzer — detect unused permissions
resource "aws_accessanalyzer_analyzer" "main" {
  analyzer_name = "${var.project}-analyzer"
  type          = "ACCOUNT"
}

We enable IAM Access Analyzer to continuously monitor for unused permissions and overly broad policies. Every function gets its own IAM role — we never share roles across functions with different access patterns. Condition keys restrict actions to specific event sources, encryption keys, or VPC endpoints.

Input Validation & Injection Prevention

Every event source (API Gateway, SQS, S3, EventBridge) is an input vector. We validate and sanitize all inputs before processing.

import { z } from 'zod';

// Strict input schemas for every endpoint
const CreateOrderSchema = z.object({
  customerId: z.string().uuid(),
  items: z.array(z.object({
    sku: z.string().regex(/^[A-Z0-9-]{3,20}$/),
    quantity: z.number().int().min(1).max(100),
  })).min(1).max(50),
  shippingAddress: z.object({
    line1: z.string().min(1).max(200),
    city: z.string().min(1).max(100),
    country: z.string().length(2),
    postal: z.string().regex(/^[A-Z0-9 -]{3,10}$/i),
  }),
  // Reject unexpected fields
}).strict();

export function validateInput<T>(schema: z.ZodSchema<T>, data: unknown): T {
  const result = schema.safeParse(data);
  if (!result.success) {
    throw new ValidationError(
      result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`)
    );
  }
  return result.data;
}

// In handler
export const handler = async (event: APIGatewayProxyEventV2) => {
  let body: unknown;
  try {
    body = JSON.parse(event.body || '{}');
  } catch {
    return { statusCode: 400, body: 'Invalid JSON' };
  }
  
  const input = validateInput(CreateOrderSchema, body);
  // input is now typed and validated
};

We use Zod for runtime schema validation with .strict() to reject unexpected fields (preventing mass assignment attacks). SQL injection is prevented by using parameterized queries exclusively — Prisma and DynamoDB SDKs handle this automatically. For S3 event triggers, we validate file names, sizes, and content types before processing.

Dependency Scanning & WAF

We integrate dependency vulnerability scanning into your CI pipeline and configure AWS WAF for API protection.

# GitHub Actions — dependency audit
- name: Audit dependencies
  run: |
    npm audit --audit-level=high
    npx better-npm-audit audit --level high

# Snyk integration for deeper scanning
- uses: snyk/actions/node@master
  with:
    command: test
    args: --severity-threshold=high
  env:
    SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

# Dependabot configuration
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: "/"
    schedule:
      interval: daily
    open-pull-requests-limit: 10
    groups:
      aws-sdk:
        patterns: ["@aws-sdk/*"]

# AWS WAF for API Gateway
resource "aws_wafv2_web_acl" "api" {
  name  = "api-protection-${var.env}"
  scope = "REGIONAL"

  default_action { allow {} }

  rule {
    name     = "aws-managed-common"
    priority = 1
    override_action { none {} }
    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }
    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "common-rules"
    }
  }

  rule {
    name     = "aws-managed-sqli"
    priority = 2
    override_action { none {} }
    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesSQLiRuleSet"
        vendor_name = "AWS"
      }
    }
    visibility_config {
      sampled_requests_enabled   = true
      cloudwatch_metrics_enabled = true
      metric_name                = "sqli-rules"
    }
  }
}

AWS WAF managed rule groups block common attacks (XSS, SQL injection, bad bots) at the edge before requests reach your Lambda functions. We configure WAF in count mode first to identify false positives, then switch to block mode after tuning. Dependabot groups related packages to reduce PR noise.

Secrets Management & Encryption

We implement secure secrets management with automatic rotation and encryption at rest using customer-managed KMS keys.

resource "aws_kms_key" "lambda" {
  description         = "Lambda environment variable encryption"
  enable_key_rotation = true
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "AllowLambdaDecrypt"
        Effect    = "Allow"
        Principal = { AWS = aws_iam_role.lambda_exec.arn }
        Action    = ["kms:Decrypt"]
        Resource  = "*"
      }
    ]
  })
}

# Secrets Manager with rotation
resource "aws_secretsmanager_secret" "db_password" {
  name       = "${var.project}/db-password/${var.env}"
  kms_key_id = aws_kms_key.lambda.id
}

resource "aws_secretsmanager_secret_rotation" "db_password" {
  secret_id           = aws_secretsmanager_secret.db_password.id
  rotation_lambda_arn = aws_lambda_function.rotate_secret.arn
  rotation_rules {
    automatically_after_days = 30
  }
}

# Lambda caches secrets to avoid per-invocation API calls
const secretCache = new Map<string, { value: string; expires: number }>();

async function getSecret(name: string): Promise<string> {
  const cached = secretCache.get(name);
  if (cached && cached.expires > Date.now()) return cached.value;
  
  const result = await secretsManager.send(
    new GetSecretValueCommand({ SecretId: name })
  );
  
  secretCache.set(name, {
    value: result.SecretString!,
    expires: Date.now() + 5 * 60 * 1000, // Cache 5 minutes
  });
  
  return result.SecretString!;
}

Secrets are cached in Lambda memory for 5 minutes to avoid Secrets Manager API costs and latency on every invocation. KMS key rotation is enabled for automatic annual key rotation. Environment variables are encrypted with the customer-managed KMS key instead of the default Lambda key, giving you full control over the encryption lifecycle. We never store secrets in environment variables directly — they are always fetched from Secrets Manager at runtime.

Why Anubiz Engineering

100% async — no calls, no meetings
Delivered in days, not weeks
Full documentation included
Production-grade from day one
Security-first approach
Post-delivery support included

Ready to get started?

Skip the research. Tell us what you need, and we'll scope it, implement it, and hand it back — fully documented and production-ready.