This check flags Lambda functions that store environment variables without a customer managed KMS key for encryption in transit. AWS encrypts these values at rest by default, but adding a CMK gives you key rotation, access control, and audit logging. The fix is to set a KMS key on the function with aws lambda update-function-configuration --kms-key-arn.
Environment variables are the easiest way to pass configuration into a Lambda function. Database hostnames, feature flags, third party API keys, and connection strings all tend to end up there. That convenience is exactly why they deserve a closer look. The lambda_envencryption check looks for functions that carry environment variables but rely solely on the default AWS managed key instead of a customer managed KMS key (CMK).
It is a small gap, but it touches a few things security and compliance teams care about: who can read secrets, how key access is logged, and whether you can rotate or revoke a key on your own schedule.
What this check detects
The check inspects each Lambda function in your account and looks for two conditions at the same time:
- The function has one or more environment variables defined.
- The function has no
KMSKeyArnset, meaning AWS falls back to the default Lambda service key for encrypting those variables.
When both are true, Lensix raises the finding. It is not saying your variables are unencrypted, AWS always encrypts them at rest. It is saying you have given up the control that comes with managing the key yourself.
Note: Lambda encrypts environment variables at rest by default using an AWS managed key (aws/lambda). The difference with a customer managed key is in-transit encryption of the values during the create and update API calls, plus full control over the key policy, rotation, and CloudTrail visibility into every decrypt operation.
Why it matters
The default key works, but it leaves three meaningful gaps.
Anyone with Lambda read access can see your secrets
With the AWS managed key, any principal that can call lambda:GetFunctionConfiguration can read the plaintext environment variables in the console or API response. There is no separate key permission gating that access. If you put a database password or third party token in an environment variable, every developer, CI role, or read-only auditor with Lambda visibility can read it.
With a customer managed key, decryption requires kms:Decrypt on that key as well. You now have a second lock, and you control who holds the key.
Warning: Environment variables are not a secrets manager. Even with a CMK, treat them as a step up from plaintext, not a replacement for AWS Secrets Manager or SSM Parameter Store when you are handling long-lived credentials.
No audit trail for who decrypted what
Calls against a customer managed key show up in CloudTrail as Decrypt events tied to the key ARN. That gives you an answer to the question every incident responder eventually asks: who accessed this secret, and when. The AWS managed key gives you far less to work with here.
You cannot rotate or revoke on your terms
If a key needs to be retired, say after an employee with broad access leaves, or after a suspected exposure, you want to rotate or disable it yourself. The AWS managed key is rotated by AWS on its own schedule and cannot be disabled. A CMK puts that lever in your hands.
The realistic attack scenario is not someone cracking AES. It is a compromised read-only IAM role quietly pulling plaintext credentials out of function configs across your account. A CMK with a tight key policy turns that into a much louder, much harder operation.
How to fix it
The remediation is to create or reuse a customer managed KMS key and attach it to the function.
Step 1: Create a KMS key (or reuse an existing one)
aws kms create-key \
--description "Lambda env var encryption" \
--tags TagKey=purpose,TagValue=lambda-env
# Give it a friendly alias
aws kms create-alias \
--alias-name alias/lambda-env-key \
--target-key-id <key-id-from-previous-output>
Step 2: Attach the key to the function
Danger: Updating function configuration triggers a brief re-publish of the function. For high-throughput production functions, do this during a low-traffic window and confirm the change in a non-production environment first.
aws lambda update-function-configuration \
--function-name my-function \
--kms-key-arn arn:aws:kms:us-east-1:111122223333:key/<key-id>
Step 3: Grant the execution role permission to decrypt
The function's execution role needs kms:Decrypt on the key, otherwise the function will fail to start because it cannot read its own environment variables.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:us-east-1:111122223333:key/<key-id>"
}
]
}
Step 4: Verify
aws lambda get-function-configuration \
--function-name my-function \
--query 'KMSKeyArn'
A non-null ARN in the output means the check will now pass.
Tip: Enable automatic annual rotation on the key so you do not have to track it manually: aws kms enable-key-rotation --key-id <key-id>. Existing data stays decryptable, and new writes use the latest key material.
Fixing it in Infrastructure as Code
If your functions are managed through Terraform, bake the key in so it is correct from the first deploy.
resource "aws_kms_key" "lambda_env" {
description = "Lambda env var encryption"
enable_key_rotation = true
deletion_window_in_days = 30
}
resource "aws_lambda_function" "app" {
function_name = "my-function"
role = aws_iam_role.lambda_exec.arn
handler = "index.handler"
runtime = "nodejs20.x"
filename = "function.zip"
kms_key_arn = aws_kms_key.lambda_env.arn
environment {
variables = {
DB_HOST = "db.internal.example.com"
}
}
}
For the Serverless Framework, set it under the provider or function block:
provider:
name: aws
kmsKeyArn: arn:aws:kms:us-east-1:111122223333:key/your-key-id
functions:
app:
handler: index.handler
environment:
DB_HOST: db.internal.example.com
How to prevent it from happening again
Catching this once is fine. Stopping it from creeping back in is better. A few options, from lightest to strictest.
Policy as code in CI
Add a Checkov or OPA rule to your pipeline so a function with environment variables and no kms_key_arn fails the build. Checkov already ships a relevant check:
checkov -d . --check CKV_AWS_173
Wire that into your pull request workflow so the gate runs before any merge to main.
Continuous detection with Lensix
IaC scanning only catches what flows through your pipeline. Functions created by hand, by another team, or by a quick console fix during an incident will slip past it. A continuous scan across the account catches those drift cases and surfaces them next to the rest of your Lambda posture.
Tip: Pair the pipeline gate with continuous scanning. The pipeline keeps new code clean, and the scan covers everything created outside it. Neither alone is enough.
Service control policies for hard enforcement
If you want a true guardrail at the org level, an SCP can deny Lambda creation or updates that omit a KMS key. This is a heavier hammer, so test it carefully in a sandbox OU before rolling it across accounts.
Best practices
- Keep real secrets out of environment variables. Use Secrets Manager or SSM Parameter Store for credentials and tokens, and fetch them at runtime. Reserve environment variables for non-sensitive config.
- Use a dedicated key per workload or team. A single shared key for every Lambda undercuts the access control benefit. Scope keys so a blast radius stays small.
- Write tight key policies. Grant
kms:Decryptonly to the execution roles that need it, not to a broad set of principals. - Turn on key rotation. Annual rotation is one API call and removes a recurring manual task.
- Monitor decrypt events. Alert on unexpected
Decryptcalls against your Lambda keys, especially from roles you do not expect to invoke the function.
None of this is heavy lifting. A key, a policy statement, and one configuration change move a function from acceptable to genuinely well governed, and the audit trail you get back is worth the few minutes it takes.

