Toxic combinations: two safe settings that are dangerous together
A toxic combination is a set of cloud settings that each look acceptable alone and become a route to your data when they sit side by side. It is the plain-language name for the reason a clean scan can still precede a breach.
A toxic combination is two or more settings that pass review on their own and open a route to something valuable when they coexist. Nobody made a mistake that a checklist would catch. Each choice had a reason. The risk lives in the overlap.
The term is used across the cloud security industry, not by one vendor. Tenable, for example, uses it on its cloud security product page for the same idea. What matters is the shape of the problem, so here it is by example.
The shape of a toxic combination
Every one has the same anatomy. There is a setting that exposes something, a setting that grants something, and a gap that connects them. Take away any one and the danger goes. Leave all three and a scanner that reads one resource at a time has nothing to report, because each resource is behaving as configured.
That is why they survive reviews. A reviewer opens a single policy or a single security group and approves it in context. The context that would make it dangerous is in a different console tab, owned by a different team, changed in a different quarter.
Example 1: the public webhook with the wide role
A team needs to receive webhooks from a payment provider. They create an AWS Lambda function with a function URL and set its authentication type to NONE, because the provider cannot sign requests the way AWS expects. The function checks a shared secret in the request header. That is a reasonable design, and AWS documents that with authentication type NONE and a public resource policy, which the console creates for you, anyone who has the URL can invoke the function.
Separately, the function's execution role was created in a hurry. It was given broad read access to the account's data services, because the first version of the function also generated reports, and nobody narrowed it afterwards.
- Setting one: a publicly invokable function. Normal for a webhook receiver.
- Setting two: a broad execution role. A slightly untidy permission, rarely flagged high.
- The gap: any bug in the function, such as unsafe handling of the request body, now runs with the broad role, and the function is reachable from the internet by design.
Fixing it does not require removing the webhook. It requires cutting the role down to the one table or queue the function writes to.
Example 2: the shared CI role and the forgotten repository
A platform team lets GitHub Actions deploy to AWS through OpenID Connect, which is the right move compared with storing long-lived keys. To save effort they write the trust condition so that any repository in the organisation can assume the deploy role.
- Setting one: a deploy role that can change production. Necessary.
- Setting two: a trust condition that matches every repository in the organisation. Convenient, and invisible in the role's permissions.
- The gap: the organisation also holds a prototype repository that a contractor can push to. A workflow added there can assume the production role.
The role's permissions pass an audit. The trust policy passes an audit if the reviewer does not know which repositories exist. The combination is what fails. The AWS attack path examples post shows the exact condition and the line that fixes it.
Example 3: the static site bucket that also receives exports
A marketing site is served from an S3 bucket that is public on purpose. Block Public Access has been turned off for that one bucket and a bucket policy allows reads from anyone. New buckets in AWS have Block Public Access on and ACLs disabled by default, so someone had to make this decision deliberately, and they did, for a bucket that holds only images and scripts.
Months later, a different team needs a place to drop a nightly configuration export that a partner can fetch. The bucket already has a working public read policy and an existing deploy pipeline, so the export lands in a folder in the same bucket.
- Setting one: a public bucket for static assets. Intended.
- Setting two: an export job writing to an existing bucket. Sensible reuse.
- The gap: the policy that made the bucket public was written for the assets, and it covers the export folder too.
No one changed the policy. The data moved under it. That is the quietest form of the pattern, because the settings never changed after review.
Why one-resource scanners cannot see them
A classic posture scanner evaluates each resource against a rule. Is this bucket public? Is this role overly broad? Is this function URL unauthenticated? Each question has a defensible answer for a resource that was designed to behave that way, so the result is either a low-severity row or nothing. The scanner is correct about every individual resource and silent about the one fact that matters.
To see a toxic combination you need three things a per-resource check lacks:
- Relationships. Which role does this function run as? Which repositories can assume that role? Which objects does that policy cover?
- Reachability. Can anyone outside actually get to the first setting? A public bucket policy with no data under it is harmless. The same policy over an export folder is not.
- A target. Something worth reaching at the end. Without that, a chain is just a diagram.
This is exactly the structure of an attack path, and it is why path analysis and toxic-combination detection are close to the same activity. The name differs by vendor. The method is a graph search over resources, permissions and routes. For how that works and where it stops, read attack path analysis.
Finding them without a tool
You can look for toxic combinations by hand, though not cheaply. Start from the targets rather than the settings: list the buckets, databases and secrets that matter. For each one, ask who can read it, then ask who can become that principal, then ask who can reach that principal from outside. Every answer that crosses a team boundary is a place to check. It is slow, it does not scale past a few dozen targets, and it goes out of date the week after you finish. That is the case for automating it, not for skipping it.
FAQ
Is a toxic combination the same as an attack path?
They overlap heavily. A toxic combination names the dangerous overlap of settings. An attack path describes the full chain from an entry point to a target, including every hop.
Do toxic combinations require a vulnerability?
No. Many consist only of configuration and permissions that work exactly as set. A vulnerability can be one link, but it is not required.
Why do they pass code and policy review?
Reviews look at one policy or one resource at a time. The danger depends on how that resource relates to others that sit outside the change being reviewed.
Can infrastructure-as-code scanning catch them?
Partly. Scanning a template sees the settings in that template. It cannot see a trust policy, role or bucket that lives in a different repository or was changed by hand, so the combination often stays hidden.
Secorvia models these combinations as paths on its security graph, from the Starter plan. The pillar on attack path analysis explains how.