Back to blog
AWSBest PracticesCloud SecurityIdentity & AccessServerless

SNS Topic Allows Global Access: Closing Public SNS Policies

Learn why an SNS topic with a wildcard principal is a data exfiltration and abuse risk, and how to scope, fix, and prevent public SNS policies on AWS.

TL;DR

This check flags any SNS topic whose access policy grants Principal: "*", which lets anyone on the internet publish messages to or subscribe to your topic. Fix it by removing the wildcard principal and scoping the policy to specific accounts, services, or VPC endpoints.

Amazon SNS sits at the center of a lot of cloud architectures. It fans out notifications to Lambda functions, pushes events to SQS queues, triggers alerts to on-call engineers, and stitches together microservices that should never talk directly. Because so much flows through it, an SNS topic with an overly permissive policy is one of the quieter ways a cloud account leaks data or invites abuse.

The SNS Topic Allows Global Access check (sns_publicaccess) looks for topic policies that grant access to a wildcard principal. When it finds one, you have a topic that anyone with the ARN can interact with.


What this check detects

Every SNS topic has an optional resource-based policy that controls who can call actions like SNS:Publish, SNS:Subscribe, and SNS:Receive. This check parses that policy and raises a finding when it sees a statement with:

  • "Effect": "Allow"
  • "Principal": "*" or "Principal": {"AWS": "*"}
  • No condition block that meaningfully restricts the wildcard (for example, no aws:SourceOwner, aws:SourceArn, or aws:SourceAccount constraint)

A statement that combines those three things is effectively public. The most common offenders are Publish and Subscribe, but a wildcard on any action is worth a hard look.

Note: A wildcard principal is not always a misconfiguration. Some AWS service integrations require Principal: "*" paired with a condition that locks the statement down, for example allowing S3 event notifications only from a specific bucket. The danger comes from a wildcard with no condition guarding it.


Why it matters

An SNS topic open to the world creates risk in two directions, and both have real consequences.

Anyone can publish to it

If SNS:Publish is open, an attacker who learns your topic ARN can inject arbitrary messages. ARNs are not secrets. They show up in CloudTrail logs, in application configs, in IaC repos, and they follow a predictable format. Once someone can publish, they can:

  • Trigger any Lambda function or webhook subscribed to the topic, with attacker-controlled payloads
  • Flood downstream SQS queues and consumers, driving up cost and causing denial of service
  • Send fake alerts or notifications to your team, which is a useful distraction during a real incident
  • Poison data pipelines that assume topic messages are trustworthy

Anyone can subscribe to it

If SNS:Subscribe is open, an attacker can register their own endpoint, an email address, an HTTP/S URL, or an SQS queue in their own account, and quietly receive every message your topic broadcasts. This is a data exfiltration path that is easy to miss because nothing in your account looks broken. Your application keeps working. The data just gets copied somewhere else too.

Topics that carry order events, user signups, billing notifications, or security alerts are exactly the ones you do not want a stranger subscribed to.

Danger: A globally subscribable topic carrying PII or financial events is a reportable data exposure in most compliance regimes. Treat any public SNS topic that handles customer data as an active incident until proven otherwise.


How to fix it

The fix is to replace the wildcard principal with specific principals, or add a condition that restricts who can use the statement. Pick the approach that matches your use case.

Step 1: Inspect the current policy

Pull the topic attributes and read the existing policy so you understand what you are changing:

aws sns get-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:111122223333:order-events \
  --query 'Attributes.Policy' \
  --output text | jq .

Look for the statement with "Principal": "*" and note which actions it allows.

Step 2: Write a scoped policy

For most topics, you want to allow publish access only from a specific account or a specific AWS service, and keep subscribe access limited to your own account. Here is a policy that restricts publishing to your own account and to a CloudWatch alarm source:

{
  "Version": "2012-10-17",
  "Id": "order-events-policy",
  "Statement": [
    {
      "Sid": "AllowOwnerFullControl",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "SNS:Publish",
      "Resource": "arn:aws:sns:us-east-1:111122223333:order-events"
    },
    {
      "Sid": "AllowCloudWatchEvents",
      "Effect": "Allow",
      "Principal": { "Service": "events.amazonaws.com" },
      "Action": "SNS:Publish",
      "Resource": "arn:aws:sns:us-east-1:111122223333:order-events"
    }
  ]
}

If you genuinely need a cross-service integration that requires a wildcard principal, always pair it with a condition. This pattern allows S3 to publish, but only from one specific bucket:

{
  "Sid": "AllowS3FromOneBucket",
  "Effect": "Allow",
  "Principal": { "Service": "s3.amazonaws.com" },
  "Action": "SNS:Publish",
  "Resource": "arn:aws:sns:us-east-1:111122223333:order-events",
  "Condition": {
    "ArnLike": { "aws:SourceArn": "arn:aws:s3:::my-uploads-bucket" },
    "StringEquals": { "aws:SourceAccount": "111122223333" }
  }
}

Step 3: Apply the new policy

Warning: Replacing the topic policy is immediate and affects all publishers and subscribers. Before you apply, confirm every legitimate publisher and subscriber is covered by the new statements, or they will start getting AuthorizationError on their next call.

Save the policy to a file and set it:

aws sns set-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:111122223333:order-events \
  --attribute-name Policy \
  --attribute-value file://order-events-policy.json

Step 4: Verify

aws sns get-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:111122223333:order-events \
  --query 'Attributes.Policy' \
  --output text | jq '.Statement[].Principal'

Confirm no statement reports "*" without a condition. Then check your existing subscriptions for anything you do not recognize, especially endpoints in other accounts:

aws sns list-subscriptions-by-topic \
  --topic-arn arn:aws:sns:us-east-1:111122223333:order-events \
  --query 'Subscriptions[].[Protocol,Endpoint]' \
  --output table

Danger: If you find a subscription pointing to an endpoint you do not own, an email, URL, or SQS ARN from an unfamiliar account, delete it and treat the topic as compromised. Run aws sns unsubscribe --subscription-arn <arn> and review CloudTrail for the Subscribe event to find out when and how it was added.


Fixing it in infrastructure as code

If your topics are managed by Terraform or CloudFormation, fix the source, not just the live resource. Otherwise the next deploy reverts your change.

Terraform

resource "aws_sns_topic" "order_events" {
  name = "order-events"
}

data "aws_iam_policy_document" "order_events" {
  statement {
    sid     = "AllowOwnerPublish"
    effect  = "Allow"
    actions = ["SNS:Publish"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::111122223333:root"]
    }

    resources = [aws_sns_topic.order_events.arn]
  }
}

resource "aws_sns_topic_policy" "order_events" {
  arn    = aws_sns_topic.order_events.arn
  policy = data.aws_iam_policy_document.order_events.json
}

CloudFormation

OrderEventsTopicPolicy:
  Type: AWS::SNS::TopicPolicy
  Properties:
    Topics:
      - !Ref OrderEventsTopic
    PolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Sid: AllowOwnerPublish
          Effect: Allow
          Principal:
            AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
          Action: "SNS:Publish"
          Resource: !Ref OrderEventsTopic

How to prevent it from happening again

One-off fixes do not scale. The goal is to make a public SNS topic impossible to ship in the first place.

Block public access at the org level

Use a Service Control Policy to deny any SNS policy change that sets a wildcard principal without a guarding condition. This stops the misconfiguration before it lands, even from accounts with broad IAM permissions.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyPublicSNSPolicies",
      "Effect": "Deny",
      "Action": "sns:SetTopicAttributes",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": { "aws:PrincipalOrgID": "o-yourorgid" }
      }
    }
  ]
}

Gate it in CI/CD with policy-as-code

Scan IaC before it reaches AWS. Tools like Checkov, tfsec, or OPA/Conftest can fail a pull request when a topic policy contains a bare wildcard. A simple Conftest rule:

package main

deny[msg] {
  resource := input.resource.aws_sns_topic_policy[name]
  contains(resource.policy, "\"*\"")
  not contains(resource.policy, "Condition")
  msg := sprintf("SNS topic policy '%s' uses a wildcard principal with no condition", [name])
}

Tip: Run Lensix continuously rather than only at deploy time. Policies drift when someone makes a quick console change during an incident and forgets to revert it. Continuous scanning catches the drift that a CI gate never sees.

Enable IAM Access Analyzer

AWS IAM Access Analyzer flags resource policies, including SNS, that grant access to external principals. Enable it per region and route findings to your security team:

aws accessanalyzer create-analyzer \
  --analyzer-name org-analyzer \
  --type ORGANIZATION

Best practices

  • Default to least privilege. Start every topic policy with access scoped to your own account, then add specific principals only as integrations require them.
  • Never use a bare wildcard. If you must use Principal: "*", always pair it with aws:SourceArn, aws:SourceAccount, or aws:SourceOwner.
  • Enable encryption. Turn on server-side encryption with a customer-managed KMS key so message contents are protected at rest. Remember that subscribers still receive plaintext, so encryption is not a substitute for a tight policy.
  • Audit subscriptions regularly. List subscribers per topic and confirm every endpoint belongs to an account or service you control.
  • Separate sensitive topics. Keep topics that carry PII or financial data in their own tightly scoped namespace, and apply stricter review to any policy change on them.
  • Tag and own your topics. Every topic should have an owner tag so that when a finding fires, there is no question about who fixes it.

An SNS topic is a small resource with a large blast radius. Closing global access takes a few minutes, and locking the door at the org and CI/CD level keeps it closed for good.