Lambda Function Environment Variables Contain No Secrets
Overview
This check scans AWS Lambda function environment variables for hardcoded secrets such as API keys, passwords, tokens, and database credentials. Prowler uses pattern-based detection to identify potential secrets stored directly in environment variable values.
Risk
Storing secrets in Lambda environment variables creates serious security vulnerabilities:
- Credential exposure: Anyone with read access to Lambda configuration can see environment variable values in plain text via the console, CLI, or API
- Log leakage: Environment variables may appear in CloudWatch logs, deployment logs, or debugging output
- Lateral movement: Attackers who obtain credentials can access connected systems (databases, APIs, other AWS services)
- Compliance violations: Storing secrets in plain-text environment variables violates PCI-DSS, HIPAA, SOC 2, and other security frameworks
Severity: Critical
Remediation Steps
Prerequisites
- AWS account access with permissions to modify Lambda functions and create secrets
- Knowledge of which environment variables contain sensitive values
Required IAM permissions
You will need the following permissions:
lambda:GetFunctionConfiguration- Read function configurationlambda:UpdateFunctionConfiguration- Modify environment variablessecretsmanager:CreateSecret- Create new secretssecretsmanager:GetSecretValue- Retrieve secret valuesiam:PutRolePolicyoriam:AttachRolePolicy- Grant Lambda access to secrets
AWS Console Method
Step 1: Identify the secrets in environment variables
- Sign in to the AWS Management Console
- Navigate to Lambda > Functions
- Select the flagged function
- Click the Configuration tab, then Environment variables
- Review the Prowler finding to identify which environment variables contain secrets
Step 2: Store secrets in AWS Secrets Manager
- Open Secrets Manager in the AWS Console
- Click Store a new secret
- For Secret type, choose:
- Credentials for Amazon RDS database (for database passwords)
- Other type of secret (for API keys, tokens, etc.)
- Enter your secret key-value pairs (e.g.,
api_key=your-actual-api-key) - Click Next
- For Secret name, enter a descriptive name (e.g.,
myapp/prod/api-credentials) - Add a description and tags if desired
- Click Next, configure rotation if desired, then click Store
- Copy the Secret ARN for the next step
Step 3: Grant Lambda permission to access the secret
- Go to IAM > Roles
- Find and select your Lambda function's execution role (shown in Lambda function configuration)
- Click Add permissions > Create inline policy
- Choose the JSON tab and paste:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "<your-secret-arn>"
}
]
}
- Replace
<your-secret-arn>with the ARN from Step 2 - Click Next, name the policy (e.g.,
SecretsManagerAccess), and click Create policy
Step 4: Remove secrets from environment variables
- Return to your Lambda function in the console
- Go to Configuration > Environment variables
- Click Edit
- Remove the environment variables containing secrets (or replace their values with the secret name reference)
- Optionally, add a non-sensitive variable pointing to the secret name (e.g.,
SECRET_NAME=myapp/prod/api-credentials) - Click Save
Step 5: Update your Lambda function code
Modify your code to retrieve secrets from Secrets Manager at runtime instead of reading environment variables.
Python example
import boto3
import json
import os
def get_secret():
client = boto3.client('secretsmanager', region_name='us-east-1')
secret_name = os.environ.get('SECRET_NAME', 'myapp/prod/api-credentials')
response = client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
def lambda_handler(event, context):
secrets = get_secret()
api_key = secrets['api_key']
# Use api_key in your code...
Node.js example
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
const client = new SecretsManagerClient({ region: 'us-east-1' });
async function getSecret() {
const secretName = process.env.SECRET_NAME || 'myapp/prod/api-credentials';
const command = new GetSecretValueCommand({ SecretId: secretName });
const response = await client.send(command);
return JSON.parse(response.SecretString);
}
exports.handler = async (event) => {
const secrets = await getSecret();
const apiKey = secrets.api_key;
// Use apiKey in your code...
};
AWS CLI (optional)
View current environment variables:
aws lambda get-function-configuration \
--function-name <your-function-name> \
--region us-east-1 \
--query 'Environment.Variables'
Create a secret in Secrets Manager:
aws secretsmanager create-secret \
--name myapp/prod/api-credentials \
--description "API credentials for my Lambda function" \
--secret-string '{"api_key":"your-api-key","db_password":"your-db-password"}' \
--region us-east-1
Get the Lambda function's execution role:
aws lambda get-function-configuration \
--function-name <your-function-name> \
--region us-east-1 \
--query 'Role' \
--output text
Add Secrets Manager access to the role:
aws iam put-role-policy \
--role-name <your-lambda-role-name> \
--policy-name SecretsManagerAccess \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:us-east-1:<account-id>:secret:myapp/prod/api-credentials-*"
}]
}'
Remove secrets from environment variables:
To clear all environment variables:
aws lambda update-function-configuration \
--function-name <your-function-name> \
--environment "Variables={}" \
--region us-east-1
To keep some variables and remove others (replace only the non-sensitive ones):
aws lambda update-function-configuration \
--function-name <your-function-name> \
--environment "Variables={SECRET_NAME=myapp/prod/api-credentials,LOG_LEVEL=INFO}" \
--region us-east-1
CloudFormation (optional)
AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda function with Secrets Manager integration (no secrets in env vars)
Parameters:
ApiKeyValue:
Type: String
NoEcho: true
Description: The API key to store in Secrets Manager
Resources:
ApiSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: myapp/prod/api-credentials
Description: API credentials for Lambda function
SecretString: !Sub '{"api_key":"${ApiKeyValue}"}'
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: MyLambdaExecutionRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: SecretsManagerAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: secretsmanager:GetSecretValue
Resource: !Ref ApiSecret
MyLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: my-secure-function
Runtime: python3.12
Handler: index.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Environment:
Variables:
# Only non-sensitive values here - the secret NAME, not the secret VALUE
SECRET_NAME: !Ref ApiSecret
LOG_LEVEL: INFO
Code:
ZipFile: |
import boto3
import json
import os
def lambda_handler(event, context):
client = boto3.client('secretsmanager')
secret_name = os.environ['SECRET_NAME']
response = client.get_secret_value(SecretId=secret_name)
secrets = json.loads(response['SecretString'])
# Use secrets in your code
return {'statusCode': 200}
Outputs:
SecretArn:
Description: ARN of the created secret
Value: !Ref ApiSecret
FunctionArn:
Description: ARN of the Lambda function
Value: !GetAtt MyLambdaFunction.Arn
Deploy the template:
aws cloudformation deploy \
--template-file lambda-secrets.yaml \
--stack-name lambda-secrets-stack \
--parameter-overrides ApiKeyValue=your-actual-api-key \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
Terraform (optional)
provider "aws" {
region = "us-east-1"
}
# Store the secret in Secrets Manager
resource "aws_secretsmanager_secret" "api_credentials" {
name = "myapp/prod/api-credentials"
description = "API credentials for Lambda function"
}
resource "aws_secretsmanager_secret_version" "api_credentials" {
secret_id = aws_secretsmanager_secret.api_credentials.id
secret_string = jsonencode({
api_key = var.api_key
db_password = var.db_password
})
}
# IAM role for Lambda
resource "aws_iam_role" "lambda_role" {
name = "my-lambda-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
# Attach basic execution policy
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Grant access to the specific secret
resource "aws_iam_role_policy" "secrets_access" {
name = "SecretsManagerAccess"
role = aws_iam_role.lambda_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "secretsmanager:GetSecretValue"
Resource = aws_secretsmanager_secret.api_credentials.arn
}]
})
}
# Lambda function - NO secrets in environment variables
resource "aws_lambda_function" "my_function" {
filename = "function.zip"
function_name = "my-secure-function"
role = aws_iam_role.lambda_role.arn
handler = "index.lambda_handler"
runtime = "python3.12"
source_code_hash = filebase64sha256("function.zip")
environment {
variables = {
# Only non-sensitive values - the secret NAME, not VALUE
SECRET_NAME = aws_secretsmanager_secret.api_credentials.name
LOG_LEVEL = "INFO"
}
}
}
# Variables - marked as sensitive
variable "api_key" {
type = string
sensitive = true
description = "API key to store in Secrets Manager"
}
variable "db_password" {
type = string
sensitive = true
description = "Database password to store in Secrets Manager"
}
Verification
After completing the remediation:
- Navigate to Lambda > Functions > your function in the AWS Console
- Go to Configuration > Environment variables
- Verify no sensitive values are present (only non-sensitive references like
SECRET_NAME) - Test your function to ensure it retrieves secrets correctly from Secrets Manager
- Re-run the Prowler check to confirm the issue is resolved
CLI verification commands
Verify environment variables no longer contain secrets:
aws lambda get-function-configuration \
--function-name <your-function-name> \
--region us-east-1 \
--query 'Environment.Variables'
Verify the secret exists in Secrets Manager:
aws secretsmanager describe-secret \
--secret-id myapp/prod/api-credentials \
--region us-east-1
Verify Lambda role has Secrets Manager permissions:
aws iam list-role-policies \
--role-name <your-lambda-role-name>
Test the Lambda function:
aws lambda invoke \
--function-name <your-function-name> \
--region us-east-1 \
output.json
cat output.json
Additional Resources
- Using AWS Secrets Manager with Lambda
- AWS Secrets Manager Best Practices
- Lambda Environment Variables
- AWS Parameters and Secrets Lambda Extension
Notes
- Environment variables are visible: Unlike Secrets Manager, Lambda environment variables are visible in plain text to anyone with
lambda:GetFunctionConfigurationpermissions. This includes the AWS Console, CLI, and API responses. - Alternative: AWS Systems Manager Parameter Store: For simpler secrets or cost savings, you can use Parameter Store with SecureString parameters instead of Secrets Manager.
- Caching for performance: Use the AWS Parameters and Secrets Lambda Extension to cache secrets locally and reduce API calls and latency.
- Secret rotation: Enable automatic rotation for database credentials and other supported secret types in Secrets Manager.
- Defense in depth: Restrict
lambda:GetFunctionConfigurationpermissions to limit who can view environment variables, and enable CloudTrail logging to monitor access. - Cold start impact: Retrieving secrets adds latency during Lambda cold starts. The Lambda extension caches secrets to minimize this impact on subsequent invocations.