What it means
KMS is unusual among AWS services in requiring permission from two directions. The caller's IAM policy must allow the action, and the key's own policy must permit the caller. Satisfying only one produces a denial, and because most services need only the first, this catches people who have correctly granted every permission they thought was involved.
On Lambda the confusing case is environment variables. If a function uses a customer-managed key to encrypt them, Lambda decrypts them using your execution role before your handler runs. Your code never calls KMS; there is no kms:Decrypt anywhere in your source; and yet the role needs that permission or the function cannot start at all. The failure lands before your first log line, which makes it look like the function is broken rather than unauthorised.
That also explains the most alarming version of this: a function that has run for months, with no deployment, suddenly failing every invocation. Nothing changed in the function. Someone disabled the key, scheduled it for deletion, or tightened its policy, and every execution environment that tries to start now fails at the decrypt step. The four exception names distinguish these — Disabled, InvalidState, NotFound, AccessDenied — and reading which one you got points directly at whether this is a policy problem or a key lifecycle problem.
The second family is more ordinary but reads as more mysterious: an SDK call that fails with a KMS error when you were talking to S3. An object encrypted with a customer-managed key requires kms:Decrypt on that key in addition to s3:GetObject, and the denial surfaces from the KMS side with a message that never mentions the bucket. When a KMS exception appears in a stack trace full of S3 or DynamoDB frames, that is what has happened — and the fix is a second statement in the policy, not a change to the one you have been staring at.
CloudWatch’s Errors metric: Whether CloudWatch’s Errors metric counts this depends on whether the error escaped your handler, which the log line alone does not settle. When Lambda cannot decrypt environment variables the invocation fails before your code runs and CloudWatch counts it. A KMSAccessDeniedException from an SDK call inside your handler is counted only if it escapes. LogStitch classified the example below from its log level rather than an extracted type.
What it looks like in CloudWatch
This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.
CloudWatch Logs Before
7 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
Error6b3f9c02-8e17-4a54-b7d1-3c0e5f2a9b46
- 15:02:18.114PLAT
START RequestId: 6b3f9c02-8e17-4a54-b7d1-3c0e5f2a9b46 Version: $LATEST
- 15:02:18.118INFO
INFO Export requested key=exports/2026-07/ledger.csv.gz
- 15:02:18.402ERROR
ERROR KMSAccessDeniedException: The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access. at throwDefaultError (/var/task/node_modules/@smithy/smithy-client/dist-cjs/index.js:867:20) at deserializeAws_restXmlGetObjectCommandError (/var/task/node_modules/@aws-sdk/client-s3/dist-cjs/index.js:4412:14)3 lines - 15:02:18.688PLAT
END RequestId: 6b3f9c02-8e17-4a54-b7d1-3c0e5f2a9b46
- 15:02:18.688PLAT
REPORT RequestId: 6b3f9c02-8e17-4a54-b7d1-3c0e5f2a9b46 Duration: 574.22 ms Billed Duration: 575 ms Memory Size: 512 MB Max Memory Used: 108 MB Status: error
The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.
How to confirm it from the logs
Look at where the error sits relative to START. If it appears before your first log line, and the text mentions environment variables, Lambda could not start the function — the failure is in the platform's own decrypt and no code of yours has run. If it appears mid-invocation with an SDK stack trace, the denial is on a key protecting a bucket, a table, a queue, or a secret.
Distinguish the four related exceptions by name, because the fixes differ. KMSAccessDeniedException is a permissions problem. KMSDisabledException means the key is disabled. KMSInvalidStateException usually means it is pending deletion. KMSNotFoundException means the key does not exist — commonly because it lived in an account or region you are no longer talking to.
Causes, most likely first
The execution role cannot use the key that encrypts the environment variables
Check whether the function has a customer-managed KMS key configured for environment variables, and whether that key's policy permits the execution role to kms:Decrypt. Lambda performs this decrypt as your role, so the role needs the grant even though your code never calls KMS.
The key was disabled, scheduled for deletion, or its policy was rewritten
Describe the key and read its state and policy. A function that has worked for months and suddenly cannot start, with no deployment in between, is nearly always a key whose state or policy changed underneath it.
The role lacks kms:Decrypt for a resource it reads
Read the stack trace. If the denial comes from inside an S3, DynamoDB, SQS or Secrets Manager call, the resource is encrypted with a customer-managed key and the role needs kms:Decrypt on that key in addition to the service permission.
The key policy does not grant access, only the IAM policy does
Check the key policy itself, not just the role. KMS requires the key's own policy to permit the principal — an IAM policy alone is not sufficient unless the key policy delegates to IAM for that account.
Fixes
Grant the execution role kms:Decrypt on the key
For encrypted environment variables the role needs kms:Decrypt on the key Lambda uses. Adding a condition scoped to the Lambda service keeps the grant narrow.
yaml- Effect: Allow
Action:
- kms:Decrypt
Resource: !GetAtt EnvKey.Arn
Condition:
StringEquals:
kms:ViaService: !Sub "lambda.${AWS::Region}.amazonaws.com"
Check the key's own policy, not just the role's
KMS evaluates the key policy as well as the caller's IAM policy. A role with kms:Decrypt in its own policy is still denied if the key policy does not permit it or delegate to IAM.
bashaws kms describe-key --key-id alias/lambda-env \
--query 'KeyMetadata.{State:KeyState,Manager:KeyManager}'
aws kms get-key-policy --key-id alias/lambda-env \
--policy-name default --output text
Add kms:Decrypt alongside the service permission for encrypted resources
Reading from an encrypted bucket or table needs both the service action and the key permission. Granting only the first produces a denial that names KMS rather than the service, which is why it reads as unrelated to the call you made.
yaml- Effect: Allow
Action: [s3:GetObject]
Resource: !Sub "${ExportBucket.Arn}/*"
- Effect: Allow
Action: [kms:Decrypt, kms:DescribeKey]
Resource: !GetAtt ExportKey.Arn
Also seen as
The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.
Related errors
Errors that show up alongside this one, or that people mistake for it.
References
- AWS Lambda Developer Guide — Securing environment variables
- AWS KMS Developer Guide — Key policies in AWS KMS
LogStitch finds this automatically, across every invocation in your account.
Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.