Skip to content

feat: add dynamic MCP servers with per-user preferences - #157

Open
harshitkgupta wants to merge 3 commits into
mainfrom
feat/dynamic-mcp-servers
Open

feat: add dynamic MCP servers with per-user preferences#157
harshitkgupta wants to merge 3 commits into
mainfrom
feat/dynamic-mcp-servers

Conversation

@harshitkgupta

@harshitkgupta harshitkgupta commented Aug 18, 2026

Copy link
Copy Markdown

Summary

  • Developers declare MCP servers in config.yaml / terraform.tfvars — each becomes an AgentCore Gateway target (streamable-HTTP MCP, NONE or OAUTH auth)
  • Per-user enable/disable via settings dialog (gear icon in chat header); preferences persisted in DynamoDB across sessions/devices
  • Agent runtime filters gateway tools per user on each request using Strands tool_filters; fail-open on DynamoDB error
  • Full CDK + Terraform parity (types, validation, gateway targets, OAuth2 providers, prefs table, API routes, runtime env/IAM)
  • Cedar policy opened to all gateway actions (tool names unknown at deploy time; per-user gating is runtime-side)
  • Cedar-policy Lambda made idempotent (handles ConflictException on retry)
  • Gateway target names shortened to mcp-{id} (Bedrock 64-char tool-name limit)

Test plan

  • tsc --noEmit (CDK + frontend)
  • cdk synth — MCP targets, OAuth2 provider, prefs table, API routes all render correctly
  • terraform validate — passes
  • Python compile check (mcp-prefs Lambda, cedar-policy Lambda, mcp_prefs.py)
  • mcp_prefs.py logic self-check (defaults, prefs override, DDB error fail-open, filter callback)
  • Live deploy to us-west-2 — stack CREATE_COMPLETE, both targets READY
  • E2E preferences API (GET defaults, PUT, GET after toggle, 400 on bad body, 401 without token)
  • Agent tool list changes per user's enabled servers (verified both directions)
  • Real MCP tool call through gateway (aws-knowledge search_documentation → real URL returned)
  • Frontend deployed to Amplify — settings dialog renders, saves, persists
  • UI walkthrough: fresh user defaults, toggle, save, verify agent uses new tool set
  • Second user: confirm independent toggle state

Allow developers to declare a catalog of MCP servers in config.yaml (CDK) or
terraform.tfvars (Terraform). Each enabled entry becomes an AgentCore Gateway
target (streamable-HTTP MCP). Per-user enable/disable toggles are persisted in
DynamoDB and surfaced through a settings dialog in the chat UI. The agent
runtime filters gateway tools per user on each request via Strands tool_filters.

Infrastructure (CDK + Terraform parity):
- McpServerConfig types + validation (NONE/OAUTH auth, https-only, no stdio)
- Gateway targets via L2 GatewayTarget.forMcpServer / native TF resource
- OAuth2 credential providers via L2 / native TF resource
- McpPrefsTable (DynamoDB) + mcp-prefs Lambda (GET/PUT /mcp-servers)
- Runtime env vars (MCP_PREFS_TABLE, MCP_SERVERS_CATALOG) + IAM grants
- API Gateway cache disabled on GET /mcp-servers (prevent cross-user leak)

Agent runtime:
- tools/mcp_prefs.py: reads user's enabled list, builds rejection filter
- tools/gateway.py: passes tool_filters to MCPClient
- Fail-open on DynamoDB error (catalog defaults used)

Frontend:
- McpServersDialog with checkbox toggles per server
- Gear icon in ChatHeader opens dialog
- mcpServerService for GET/PUT calls

Also:
- Cedar policy opened to all gateway tools (MCP tool names unknown at deploy)
- cedar-policy Lambda: idempotent CreatePolicyEngine (handle ConflictException)
- Target names shortened to mcp-{id} (Bedrock 64-char tool-name limit)
@harshitkgupta
harshitkgupta requested a review from a team August 18, 2026 04:36
@github-actions github-actions Bot added documentation Improvements or additions to documentation backend infrastructure frontend labels Aug 18, 2026
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Latest scan for commit: 6d0bebe | Updated: 2026-08-18 05:09:31 UTC

Security Scan Results

Scan Metadata

  • Project: ASH
  • Scan executed: 2026-08-18T05:09:19+00:00
  • ASH version: 3.2.2

Summary

Scanner Results

The table below shows findings by scanner, with status based on severity thresholds and dependencies:

Column Explanations:

Severity Levels (S/C/H/M/L/I):

  • Suppressed (S): Security findings that have been explicitly suppressed/ignored and don't affect the scanner's pass/fail status
  • Critical (C): The most severe security vulnerabilities requiring immediate remediation (e.g., SQL injection, remote code execution)
  • High (H): Serious security vulnerabilities that should be addressed promptly (e.g., authentication bypasses, privilege escalation)
  • Medium (M): Moderate security risks that should be addressed in normal development cycles (e.g., weak encryption, input validation issues)
  • Low (L): Minor security concerns with limited impact (e.g., information disclosure, weak recommendations)
  • Info (I): Informational findings for awareness with minimal security risk (e.g., code quality suggestions, best practice recommendations)

Other Columns:

  • Time: Duration taken by each scanner to complete its analysis
  • Action: Total number of actionable findings at or above the configured severity threshold that require attention

Scanner Results:

  • PASSED: Scanner found no security issues at or above the configured severity threshold - code is clean for this scanner
  • FAILED: Scanner found security vulnerabilities at or above the threshold that require attention and remediation
  • MISSING: Scanner could not run because required dependencies/tools are not installed or available
  • SKIPPED: Scanner was intentionally disabled or excluded from this scan
  • ERROR: Scanner encountered an execution error and could not complete successfully

Severity Thresholds (Thresh Column):

  • CRITICAL: Only Critical severity findings cause scanner to fail
  • HIGH: High and Critical severity findings cause scanner to fail
  • MEDIUM (MED): Medium, High, and Critical severity findings cause scanner to fail
  • LOW: Low, Medium, High, and Critical severity findings cause scanner to fail
  • ALL: Any finding of any severity level causes scanner to fail

Threshold Source: Values in parentheses indicate where the threshold is configured:

  • (g) = global: Set in the global_settings section of ASH configuration
  • (c) = config: Set in the individual scanner configuration section
  • (s) = scanner: Default threshold built into the scanner itself

Statistics calculation:

  • All statistics are calculated from the final aggregated SARIF report
  • Suppressed findings are counted separately and do not contribute to actionable findings
  • Scanner status is determined by comparing actionable findings to the threshold
Scanner S C H M L I Time Action Result Thresh
bandit 0 0 0 0 0 0 627ms 0 PASSED MED (g)
cdk-nag 0 0 0 0 0 0 8.4s 0 PASSED MED (g)
cfn-nag 0 0 0 0 0 0 38ms 0 PASSED MED (g)
checkov 0 30 0 0 0 0 11.0s 30 FAILED MED (g)
detect-secre… 0 0 0 0 0 0 1.2s 0 PASSED MED (g)
grype 0 0 0 0 0 0 1m 4s 0 PASSED MED (g)
npm-audit 0 0 0 0 0 0 248ms 0 PASSED MED (g)
opengrep 12 10 0 0 0 0 21.4s 10 FAILED MED (g)
semgrep 0 0 0 0 0 0 <1ms 0 MISSING MED (g)
syft 0 0 0 0 0 0 2.2s 0 PASSED MED (g)

Detailed Findings

Show 40 actionable findings

Finding 1: CKV_AWS_119

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_119
  • Location: infra-terraform/modules/backend/feedback.tf:13-54

Description:
Ensure DynamoDB Tables are encrypted using a KMS Customer Managed CMK

Code Snippet:

resource "aws_dynamodb_table" "feedback" {
  name         = "${var.stack_name_base}-feedback"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "feedbackId"

  attribute {
    name = "feedbackId"
    type = "S"
  }

  attribute {
    name = "feedbackType"
    type = "S"
  }

  attribute {
    name = "timestamp"
    type = "N"
  }

  # GSI for querying by feedbackType with timestamp sorting
  global_secondary_index {
    name            = "feedbackType-timestamp-index"
    hash_key        = "feedbackType"
    range_key       = "timestamp"
    projection_type = "ALL"
  }

  # Deletion protection disabled (allows terraform destroy)
  deletion_protection_enabled = false

  # Point-in-time recovery
  point_in_time_recovery {
    enabled = true
  }

  # Server-side encryption (AWS managed)
  server_side_encryption {
    enabled = true
  }

}

Finding 2: CKV_AWS_158

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_158
  • Location: infra-terraform/modules/backend/feedback.tf:60-64

Description:
Ensure that CloudWatch Log Group is encrypted by KMS

Code Snippet:

resource "aws_cloudwatch_log_group" "feedback_lambda" {
  name              = "/aws/lambda/${var.stack_name_base}-feedback"
  retention_in_days = local.log_retention_days

}

Finding 3: CKV_AWS_173

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_173
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Check encryption settings for Lambda environmental variable

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 4: CKV_AWS_116

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_116
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ)

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 5: CKV_AWS_272

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_272
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Ensure AWS Lambda function is configured to validate code-signing

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 6: CKV_AWS_115

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_115
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Ensure that AWS Lambda function is configured for function-level concurrent execution limit

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 7: CKV_AWS_117

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_117
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
Ensure that AWS Lambda function is configured inside a VPC

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 8: CKV_AWS_50

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_50
  • Location: infra-terraform/modules/backend/feedback.tf:158-182

Description:
X-Ray tracing is enabled for Lambda

Code Snippet:

resource "aws_lambda_function" "feedback" {
  function_name = "${var.stack_name_base}-feedback"
  role          = aws_iam_role.feedback_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.feedback_lambda.output_path
  source_code_hash = data.archive_file.feedback_lambda.output_base64sha256

  # Lambda Powertools layer
  layers = [local.powertools_layer_arn]

  # Environment variables
  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.feedback.name
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.feedback_lambda]

}

Finding 9: CKV_AWS_237

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_237
  • Location: infra-terraform/modules/backend/feedback.tf:188-196

Description:
Ensure Create before destroy for API Gateway

Code Snippet:

resource "aws_api_gateway_rest_api" "feedback" {
  name        = "${var.stack_name_base}-feedback-api"
  description = "API Gateway for feedback collection"

  endpoint_configuration {
    types = ["REGIONAL"]
  }

}

Finding 10: CKV_AWS_120

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_120
  • Location: infra-terraform/modules/backend/feedback.tf:348-373

Description:
Ensure API Gateway caching is enabled

Code Snippet:

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  deployment_id = aws_api_gateway_deployment.feedback.id

  # Access logs
  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway_access.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      ip               = "$context.identity.sourceIp"
      caller           = "$context.identity.caller"
      user             = "$context.identity.user"
      requestTime      = "$context.requestTime"
      httpMethod       = "$context.httpMethod"
      resourcePath     = "$context.resourcePath"
      status           = "$context.status"
      protocol         = "$context.protocol"
      responseLength   = "$context.responseLength"
      integrationError = "$context.integrationErrorMessage"
    })
  }


  depends_on = [aws_cloudwatch_log_group.api_gateway_access]
}

Finding 11: CKV_AWS_73

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_73
  • Location: infra-terraform/modules/backend/feedback.tf:348-373

Description:
Ensure API Gateway has X-Ray Tracing enabled

Code Snippet:

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  deployment_id = aws_api_gateway_deployment.feedback.id

  # Access logs
  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway_access.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      ip               = "$context.identity.sourceIp"
      caller           = "$context.identity.caller"
      user             = "$context.identity.user"
      requestTime      = "$context.requestTime"
      httpMethod       = "$context.httpMethod"
      resourcePath     = "$context.resourcePath"
      status           = "$context.status"
      protocol         = "$context.protocol"
      responseLength   = "$context.responseLength"
      integrationError = "$context.integrationErrorMessage"
    })
  }


  depends_on = [aws_cloudwatch_log_group.api_gateway_access]
}

Finding 12: CKV_AWS_158

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_158
  • Location: infra-terraform/modules/backend/feedback.tf:376-380

Description:
Ensure that CloudWatch Log Group is encrypted by KMS

Code Snippet:

resource "aws_cloudwatch_log_group" "api_gateway_access" {
  name              = "/aws/apigateway/${var.stack_name_base}-feedback-api/access-logs"
  retention_in_days = local.log_retention_days

}

Finding 13: CKV_AWS_28

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_28
  • Location: infra-terraform/modules/backend/mcp_servers.tf:116-133

Description:
Ensure DynamoDB point in time recovery (backup) is enabled

Code Snippet:

resource "aws_dynamodb_table" "mcp_prefs" {
  count = local.mcp_feature_enabled ? 1 : 0

  name         = "${var.stack_name_base}-mcp-prefs"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "userId"

  attribute {
    name = "userId"
    type = "S"
  }

  deletion_protection_enabled = false

  server_side_encryption {
    enabled = true
  }
}

Finding 14: CKV_AWS_119

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_119
  • Location: infra-terraform/modules/backend/mcp_servers.tf:116-133

Description:
Ensure DynamoDB Tables are encrypted using a KMS Customer Managed CMK

Code Snippet:

resource "aws_dynamodb_table" "mcp_prefs" {
  count = local.mcp_feature_enabled ? 1 : 0

  name         = "${var.stack_name_base}-mcp-prefs"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "userId"

  attribute {
    name = "userId"
    type = "S"
  }

  deletion_protection_enabled = false

  server_side_encryption {
    enabled = true
  }
}

Finding 15: CKV_AWS_158

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_158
  • Location: infra-terraform/modules/backend/mcp_servers.tf:139-144

Description:
Ensure that CloudWatch Log Group is encrypted by KMS

Code Snippet:

resource "aws_cloudwatch_log_group" "mcp_prefs_lambda" {
  count = local.mcp_feature_enabled ? 1 : 0

  name              = "/aws/lambda/${var.stack_name_base}-mcp-prefs"
  retention_in_days = local.log_retention_days
}

Finding 16: CKV_AWS_173

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_173
  • Location: infra-terraform/modules/backend/mcp_servers.tf:207-231

Description:
Check encryption settings for Lambda environmental variable

Code Snippet:

resource "aws_lambda_function" "mcp_prefs" {
  count = local.mcp_feature_enabled ? 1 : 0

  function_name = "${var.stack_name_base}-mcp-prefs"
  role          = aws_iam_role.mcp_prefs_lambda[0].arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.mcp_prefs_lambda[0].output_path
  source_code_hash = data.archive_file.mcp_prefs_lambda[0].output_base64sha256

  layers = [local.powertools_layer_arn]

  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.mcp_prefs[0].name
      MCP_SERVERS_CATALOG  = local.mcp_servers_catalog
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.mcp_prefs_lambda]
}

Finding 17: CKV_AWS_116

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_116
  • Location: infra-terraform/modules/backend/mcp_servers.tf:207-231

Description:
Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ)

Code Snippet:

resource "aws_lambda_function" "mcp_prefs" {
  count = local.mcp_feature_enabled ? 1 : 0

  function_name = "${var.stack_name_base}-mcp-prefs"
  role          = aws_iam_role.mcp_prefs_lambda[0].arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.mcp_prefs_lambda[0].output_path
  source_code_hash = data.archive_file.mcp_prefs_lambda[0].output_base64sha256

  layers = [local.powertools_layer_arn]

  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.mcp_prefs[0].name
      MCP_SERVERS_CATALOG  = local.mcp_servers_catalog
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.mcp_prefs_lambda]
}

Finding 18: CKV_AWS_272

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_272
  • Location: infra-terraform/modules/backend/mcp_servers.tf:207-231

Description:
Ensure AWS Lambda function is configured to validate code-signing

Code Snippet:

resource "aws_lambda_function" "mcp_prefs" {
  count = local.mcp_feature_enabled ? 1 : 0

  function_name = "${var.stack_name_base}-mcp-prefs"
  role          = aws_iam_role.mcp_prefs_lambda[0].arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.mcp_prefs_lambda[0].output_path
  source_code_hash = data.archive_file.mcp_prefs_lambda[0].output_base64sha256

  layers = [local.powertools_layer_arn]

  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.mcp_prefs[0].name
      MCP_SERVERS_CATALOG  = local.mcp_servers_catalog
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.mcp_prefs_lambda]
}

Finding 19: CKV_AWS_115

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_115
  • Location: infra-terraform/modules/backend/mcp_servers.tf:207-231

Description:
Ensure that AWS Lambda function is configured for function-level concurrent execution limit

Code Snippet:

resource "aws_lambda_function" "mcp_prefs" {
  count = local.mcp_feature_enabled ? 1 : 0

  function_name = "${var.stack_name_base}-mcp-prefs"
  role          = aws_iam_role.mcp_prefs_lambda[0].arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.mcp_prefs_lambda[0].output_path
  source_code_hash = data.archive_file.mcp_prefs_lambda[0].output_base64sha256

  layers = [local.powertools_layer_arn]

  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.mcp_prefs[0].name
      MCP_SERVERS_CATALOG  = local.mcp_servers_catalog
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.mcp_prefs_lambda]
}

Finding 20: CKV_AWS_117

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_117
  • Location: infra-terraform/modules/backend/mcp_servers.tf:207-231

Description:
Ensure that AWS Lambda function is configured inside a VPC

Code Snippet:

resource "aws_lambda_function" "mcp_prefs" {
  count = local.mcp_feature_enabled ? 1 : 0

  function_name = "${var.stack_name_base}-mcp-prefs"
  role          = aws_iam_role.mcp_prefs_lambda[0].arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.mcp_prefs_lambda[0].output_path
  source_code_hash = data.archive_file.mcp_prefs_lambda[0].output_base64sha256

  layers = [local.powertools_layer_arn]

  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.mcp_prefs[0].name
      MCP_SERVERS_CATALOG  = local.mcp_servers_catalog
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.mcp_prefs_lambda]
}

Finding 21: CKV_AWS_50

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_50
  • Location: infra-terraform/modules/backend/mcp_servers.tf:207-231

Description:
X-Ray tracing is enabled for Lambda

Code Snippet:

resource "aws_lambda_function" "mcp_prefs" {
  count = local.mcp_feature_enabled ? 1 : 0

  function_name = "${var.stack_name_base}-mcp-prefs"
  role          = aws_iam_role.mcp_prefs_lambda[0].arn
  handler       = "index.handler"
  runtime       = "python3.13"
  timeout       = 30
  memory_size   = 256

  filename         = data.archive_file.mcp_prefs_lambda[0].output_path
  source_code_hash = data.archive_file.mcp_prefs_lambda[0].output_base64sha256

  layers = [local.powertools_layer_arn]

  environment {
    variables = {
      TABLE_NAME           = aws_dynamodb_table.mcp_prefs[0].name
      MCP_SERVERS_CATALOG  = local.mcp_servers_catalog
      CORS_ALLOWED_ORIGINS = "${var.frontend_url},http://localhost:3000"
    }
  }

  depends_on = [aws_cloudwatch_log_group.mcp_prefs_lambda]
}

Finding 22: CKV_AWS_225

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_AWS_225
  • Location: infra-terraform/modules/backend/mcp_servers.tf:335-345

Description:
Ensure API Gateway method setting caching is enabled

Code Snippet:

resource "aws_api_gateway_method_settings" "mcp_servers_get" {
  count = local.mcp_feature_enabled ? 1 : 0

  rest_api_id = aws_api_gateway_rest_api.feedback.id
  stage_name  = aws_api_gateway_stage.prod.stage_name
  method_path = "mcp-servers/GET"

  settings {
    caching_enabled = false
  }
}

Finding 23: CKV2_AWS_5

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_5
  • Location: infra-terraform/modules/backend/runtime.tf:380-390

Description:
Ensure that Security Groups are attached to another resource

Code Snippet:

resource "aws_security_group" "runtime_default" {
  count = var.backend_network_mode == "VPC" && length(var.backend_vpc_security_group_ids) == 0 ? 1 : 0

  name        = "${var.stack_name_base}-agentcore-runtime-sg"
  description = "Default security group for AgentCore Runtime VPC deployment"
  vpc_id      = var.backend_vpc_id

  tags = {
    Name = "${var.stack_name_base}-agentcore-runtime-sg"
  }
}

Finding 24: CKV2_AWS_4

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_4
  • Location: infra-terraform/modules/backend/feedback.tf:348-373

Description:
Ensure API Gateway stage have logging level defined as appropriate

Code Snippet:

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  deployment_id = aws_api_gateway_deployment.feedback.id

  # Access logs
  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway_access.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      ip               = "$context.identity.sourceIp"
      caller           = "$context.identity.caller"
      user             = "$context.identity.user"
      requestTime      = "$context.requestTime"
      httpMethod       = "$context.httpMethod"
      resourcePath     = "$context.resourcePath"
      status           = "$context.status"
      protocol         = "$context.protocol"
      responseLength   = "$context.responseLength"
      integrationError = "$context.integrationErrorMessage"
    })
  }


  depends_on = [aws_cloudwatch_log_group.api_gateway_access]
}

Finding 25: CKV2_AWS_51

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_51
  • Location: infra-terraform/modules/backend/feedback.tf:348-373

Description:
Ensure AWS API Gateway endpoints uses client certificate authentication

Code Snippet:

resource "aws_api_gateway_stage" "prod" {
  stage_name    = "prod"
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  deployment_id = aws_api_gateway_deployment.feedback.id

  # Access logs
  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway_access.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      ip               = "$context.identity.sourceIp"
      caller           = "$context.identity.caller"
      user             = "$context.identity.user"
      requestTime      = "$context.requestTime"
      httpMethod       = "$context.httpMethod"
      resourcePath     = "$context.resourcePath"
      status           = "$context.status"
      protocol         = "$context.protocol"
      responseLength   = "$context.responseLength"
      integrationError = "$context.integrationErrorMessage"
    })
  }


  depends_on = [aws_cloudwatch_log_group.api_gateway_access]
}

Finding 26: CKV2_AWS_53

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_53
  • Location: infra-terraform/modules/backend/feedback.tf:227-233

Description:
Ensure AWS API gateway request is validated

Code Snippet:

resource "aws_api_gateway_method" "post_feedback" {
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  resource_id   = aws_api_gateway_resource.feedback.id
  http_method   = "POST"
  authorization = "COGNITO_USER_POOLS"
  authorizer_id = aws_api_gateway_authorizer.cognito.id
}

Finding 27: CKV2_AWS_53

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_53
  • Location: infra-terraform/modules/backend/feedback.tf:236-241

Description:
Ensure AWS API gateway request is validated

Code Snippet:

resource "aws_api_gateway_method" "options_feedback" {
  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  resource_id   = aws_api_gateway_resource.feedback.id
  http_method   = "OPTIONS"
  authorization = "NONE"
}

Finding 28: CKV2_AWS_53

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV2_AWS_53
  • Location: infra-terraform/modules/backend/mcp_servers.tf:255-263

Description:
Ensure AWS API gateway request is validated

Code Snippet:

resource "aws_api_gateway_method" "mcp_servers" {
  for_each = local.mcp_feature_enabled ? toset(["GET", "PUT"]) : toset([])

  rest_api_id   = aws_api_gateway_rest_api.feedback.id
  resource_id   = aws_api_gateway_resource.mcp_servers[0].id
  http_method   = each.value
  authorization = "COGNITO_USER_POOLS"
  authorizer_id = aws_api_gateway_authorizer.cognito.id
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend documentation Improvements or additions to documentation frontend infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant