This check flags customer-managed KMS keys whose key policy grants access to any AWS principal ("AWS": "*"). That can let attackers in other accounts encrypt or decrypt your data. Fix it by scoping the policy to specific account IDs, roles, or users instead of a wildcard.
KMS keys sit at the heart of nearly everything sensitive you store in AWS: encrypted S3 objects, EBS volumes, RDS snapshots, Secrets Manager secrets, and more. The key policy is the primary access control for a KMS key, and unlike IAM policies, it is the one resource that does not defer to your account's IAM by default. If a key policy says "anyone can use me," then anyone with a path to that key can.
The account_kmspublic check looks at the resource policy attached to each customer-managed key (CMK) and raises a finding when the policy allows access from a wildcard principal. This post explains exactly what that means, why it is dangerous, and how to lock it down without breaking the services that rely on the key.
What this check detects
Every KMS key has a key policy, a JSON document that defines who can administer the key and who can use it for cryptographic operations. The check parses each key policy and flags any statement where:
- The
EffectisAllow, and - The
Principalis set to"*"or{"AWS": "*"}, and - There is no
Conditionblock that meaningfully restricts who that wildcard resolves to (for example, a missingaws:PrincipalAccountorkms:CallerAccountcondition).
A statement like this is exactly what triggers the finding:
{
"Sid": "AllowEverything",
"Effect": "Allow",
"Principal": "*",
"Action": "kms:*",
"Resource": "*"
}
Note: A wildcard principal in a KMS key policy does not literally mean "the entire internet." KMS is not internet-facing the way an S3 bucket can be. It means "any IAM principal in any AWS account, as long as they also have a matching IAM permission to call KMS." That second condition is what trips people up, because it is far easier to satisfy than most teams assume.
Why it matters
The danger comes from how KMS evaluates access. For a principal to use a key, access must be granted in both the key policy and the principal's IAM permissions, with one important exception: when the key policy grants access directly to a principal (or to *), the key policy alone can be sufficient for principals in the key's own account, and it opens a cross-account door for everyone else.
Here is the realistic attack path:
- You leave a key policy with
Principal: "*"andAction: kms:Decrypt. - An attacker compromises credentials for an IAM role in any AWS account, including their own attacker-controlled account.
- If that role has a broad IAM policy like
kms:DecryptonResource: "*", the cross-account call now succeeds, because your key policy explicitly allows it. - The attacker decrypts your data, or worse, calls
kms:ScheduleKeyDeletionif the wildcard also covers administrative actions.
Danger: A wildcard policy that includes kms:ScheduleKeyDeletion or kms:DisableKey is a data-destruction risk, not just a confidentiality risk. If an attacker schedules deletion of a key protecting your RDS snapshots or S3 data, you can permanently lose access to that data once the waiting period elapses. There is no recovery after a KMS key is deleted.
Beyond the direct breach, this is a compliance failure. PCI DSS, HIPAA, SOC 2, and CIS AWS Foundations all expect cryptographic keys to be restricted to least-privilege access. A globally accessible key policy is an easy audit finding and a hard one to explain away.
How to fix it
The fix is to replace the wildcard principal with the specific principals that genuinely need the key. Work through it in three steps: find out who is actually using the key, write a scoped policy, then apply it.
Step 1: Identify the offending key and current policy
List your customer-managed keys, then pull the current policy:
# List customer-managed keys
aws kms list-keys --query 'Keys[].KeyId' --output text
# View the key policy for a specific key
aws kms get-key-policy \
--key-id 1234abcd-12ab-34cd-56ef-1234567890ab \
--policy-name default \
--output text
Step 2: Confirm who actually uses the key
Before tightening the policy, check CloudTrail so you do not break a live workload. KMS data events show every Decrypt, GenerateDataKey, and similar call along with the calling principal.
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue=1234abcd-12ab-34cd-56ef-1234567890ab \
--max-results 50 \
--query 'Events[].{Time:EventTime,User:Username,Event:EventName}' \
--output table
Warning: KMS records cryptographic operations as CloudTrail data events only if you have data event logging enabled for KMS. If you see no events, do not assume the key is unused. Enable KMS data events in your trail and observe for a few days before locking the policy down, or you risk an outage when a scheduled job next tries to use the key.
Step 3: Write a scoped key policy
A sane baseline policy gives full administrative control to your account root (so IAM can manage the key) and grants usage only to the specific roles that need it. Replace the wildcard with something like this:
{
"Version": "2012-10-17",
"Id": "key-policy-scoped",
"Statement": [
{
"Sid": "EnableIAMAdmin",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "AllowKeyUsage",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::111122223333:role/app-encryption-role",
"arn:aws:iam::111122223333:role/backup-service-role"
]
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}
Note: Granting kms:* to your account root (:root) is not the same as a wildcard principal. It delegates control back to your account's IAM, so you can manage usage with regular IAM policies, condition keys, and grants. This is the AWS-recommended default and is safe, as long as your IAM is itself well governed.
If you need genuine cross-account access, do not use *. Name the external account explicitly and add a condition to be defensive:
{
"Sid": "AllowSpecificExternalAccount",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:root"
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:CallerAccount": "444455556666"
}
}
}
Step 4: Apply the new policy
Danger: put-key-policy overwrites the entire key policy. If you remove your own administrative access, you can lock yourself out of the key with no easy recovery. Always include the EnableIAMAdmin statement above and verify the JSON before applying it to a production key.
aws kms put-key-policy \
--key-id 1234abcd-12ab-34cd-56ef-1234567890ab \
--policy-name default \
--policy file://scoped-key-policy.json
Confirm the change took effect:
aws kms get-key-policy \
--key-id 1234abcd-12ab-34cd-56ef-1234567890ab \
--policy-name default \
--output text | jq
How to prevent it from happening again
One-off fixes do not hold. Wildcard key policies usually creep back in through copied templates or "just make it work" debugging. Catch them before they ship.
Define keys in IaC with scoped policies
Manage keys in Terraform or CloudFormation so the policy is reviewable in a pull request. A Terraform example:
data "aws_caller_identity" "current" {}
resource "aws_kms_key" "app" {
description = "App data encryption key"
deletion_window_in_days = 30
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "EnableIAMAdmin"
Effect = "Allow"
Principal = { AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" }
Action = "kms:*"
Resource = "*"
},
{
Sid = "AllowAppUsage"
Effect = "Allow"
Principal = { AWS = aws_iam_role.app.arn }
Action = ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"]
Resource = "*"
}
]
})
}
Gate it in CI/CD with policy-as-code
Add a check to your pipeline that rejects any KMS policy with a wildcard principal. With Checkov:
checkov -d . --check CKV_AWS_33
Or write a targeted OPA/Conftest rule that scans your plan output:
package kms
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_kms_key"
policy := json.unmarshal(resource.change.after.policy)
statement := policy.Statement[_]
statement.Effect == "Allow"
statement.Principal == "*"
msg := sprintf("KMS key %s allows a wildcard principal", [resource.address])
}
Tip: IAM Access Analyzer has a dedicated feature for this. Its external access findings will automatically flag any KMS key whose policy grants access outside your account or organization, including wildcard principals. Turn on an organization-level analyzer and route findings to Security Hub so you get continuous coverage with zero pipeline work. Lensix will also keep flagging account_kmspublic on every scan until the policy is scoped.
Use SCPs as a backstop
A Service Control Policy can deny the creation of overly permissive key policies across the whole org, so a single misconfigured account cannot expose a key. Pair this with a condition that prevents principals outside your organization from being added.
Best practices for KMS key policies
- Never use
Principal: "*"in a key policy. If you think you need it, you almost certainly want a named role or the account root instead. - Separate key administrators from key users. The team that can schedule deletion should not be the same as the service that encrypts data.
- Split usage actions from admin actions. Grant
kms:Encryptandkms:Decryptto applications, and keepkms:ScheduleKeyDeletion,kms:PutKeyPolicy, andkms:DisableKeyreserved for a small admin group. - Prefer grants for temporary, fine-grained access rather than editing the key policy each time a workload needs short-lived access.
- Add condition keys for cross-account access. Use
kms:CallerAccount,aws:PrincipalOrgID, orkms:ViaServiceto constrain who and how a key is used. - Enable key rotation and CloudTrail data events. Rotation limits blast radius over time, and data events give you the audit trail you need to detect misuse.
- Use the
kms:ViaServicecondition to ensure a key can only be used through an expected service, for example only via S3 in a specific region.
A KMS key policy is the last line of defense for your encrypted data. Treat a wildcard principal the same way you would treat a public S3 bucket holding production data: an incident waiting to be reported, not a convenience.
Scope every key to the principals that need it, enforce that rule in your pipeline, and let continuous scanning catch the ones that slip through. The fix takes minutes. The breach it prevents does not.

