From sre-skills
Audits AWS security groups for multi-hop lateral movement paths. Builds a directed reachability graph from SG references and 0.0.0.0/0 rules, reporting shortest path, blast radius, and pivot hubs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/sre-skills:sg-deceptive-reachability-auditorThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Reachability-audit skill for a fleet of AWS security groups. Takes the
FAILURE_MODES.mdfixtures/01-orphaned-front-internal-cidr/instances.jsonfixtures/01-orphaned-front-internal-cidr/meta.jsonfixtures/01-orphaned-front-internal-cidr/security-groups.jsonfixtures/02-public-alb-no-sg-ref/instances.jsonfixtures/02-public-alb-no-sg-ref/meta.jsonfixtures/02-public-alb-no-sg-ref/security-groups.jsonfixtures/03-disjoint-public-vpn-islands/instances.jsonfixtures/03-disjoint-public-vpn-islands/meta.jsonfixtures/03-disjoint-public-vpn-islands/security-groups.jsonfixtures/04-broken-segment-midchain/instances.jsonfixtures/04-broken-segment-midchain/meta.jsonfixtures/04-broken-segment-midchain/security-groups.jsonfixtures/05-six-hop-cdn-waf-gw-app-svc-db/instances.jsonfixtures/05-six-hop-cdn-waf-gw-app-svc-db/meta.jsonfixtures/05-six-hop-cdn-waf-gw-app-svc-db/security-groups.jsonfixtures/06-compromised-ci-runner-deep/instances.jsonfixtures/06-compromised-ci-runner-deep/meta.jsonfixtures/06-compromised-ci-runner-deep/security-groups.jsonfixtures/07-five-hop-ingress-mesh-broker-db/instances.jsonReachability-audit skill for a fleet of AWS security groups. Takes the
describe-security-groups output for the fleet (plus describe-instances for the
instance-to-SG membership), composes the SG-to-SG references into a directed graph,
and answers one question a per-rule read cannot: from a named entry point, what can
actually be reached, and does any path reach the crown-jewel tier. It returns the
shortest path, the blast radius, and any pivot hub, ranked by severity, then names
exactly where the SG graph stops being able to answer the question.
The job is a graph problem, and the whole point of the skill is the composition the
graph makes visible. A per-rule read sees "app accepts from web" and "db accepts from
app" as two individually fine rules and never assembles that internet -> web -> app -> db is one reachable path. In a fleet of 10-13 security groups, that chain is buried
among scoped tiers (bastion, monitoring, ci, ssm) and app-fed leaves (cache, queue,
logs), and the loud 0.0.0.0/0 rule on the front door draws the eye away from it. This
skill composes the edges instead of clearing each rule in isolation.
It reads the static configuration of a fleet of security groups plus the
instance-to-SG membership. Both are EC2 control-plane reads
(describe-security-groups, describe-instances). That is the entire input. The audit
is correct and complete for what the SG graph can tell you, and it is explicit about
the rest. Reachability-on-paper is not exploitability, and every audit ends by naming
the joins it cannot make:
Every audit ends by naming these. A clean (segmented) fleet still gets a boundary section, because a network-segmented fleet is not a proven-safe system.
Build a directed graph over security groups. An edge A -> B exists when SG B has
an ingress rule whose UserIdGroupPairs includes SG A (B accepts traffic from A),
meaning a host in A can reach a host in B. A synthetic node internet has an edge
internet -> X for every SG X with a 0.0.0.0/0 (or ::/0) ingress rule.
From the entry point named for the audit (internet, or a compromised instance id that
resolves to that instance's SGs), compute the transitive closure with BFS. A visited set
makes cycles terminate. The findings fall out of the closure.
Before any judgment, turn the JSON into the graph:
IpPermissions (ingress). Every UserIdGroupPairs entry naming
another SG in the fleet is an incoming edge: referenced-SG -> this-SG. This is
the step a naive read skips. The SG-reference arrays are where the chain lives.0.0.0.0/0 / ::/0 IpRanges / Ipv6Ranges entry makes the SG
internet-facing: internet -> this-SG.describe-instances for the instance-to-SG membership, so the entry point (a
compromised host) resolves to a set of start SGs, and so a tier with no running host
can be flagged as a path to nothing at the boundary.tier / Name tag, then GroupName, then GroupId, so the path
reads as internet -> web -> app -> db, not as a list of sg- ids.Run BFS from the entry's start set. The reachable set is the closure minus the start. If the crown-jewel tier is in the closure, compute the shortest path (fewest hops) to it and report it as the headline:
internet -> cdn -> waf -> gw -> app -> svc -> db). No single ingress rule is alarming; each tier accepting the tier in front
of it is routine. The edges compose into one path a per-rule read never assembles.
This is the lateral-movement chain the audit exists to surface, and on a needle fleet
it is the primary finding, named end to end, not a footnote under the loud public
rule.This is the half of the skill that the naive read gets wrong in the other direction. A segmented, orphaned, or broken fleet where no path reaches the crown jewel is CLEAN, and the audit must say so instead of manufacturing a path. The same composition discipline is what proves it. Specifically:
0.0.0.0/0 is the expected ingress, not the lateral
path and not the headline.internet -> db route.On a clean fleet the audit reports: no reachable path to the crown jewel, the bounded blast radius (and the boundary it cannot cross), and the join checks that would confirm the segmentation holds. It does not invent a critical.
Order findings by severity (critical, high). For each: the path/SG it is grounded in, what it means, and the fix. Then list the boundary from step "What this skill reads." A clean fleet still gets a boundary section.
| Severity | Meaning |
|---|---|
| critical | A composed path from the entry reaches the crown-jewel tier. P1. |
| high | A foothold at the entry can pivot laterally, or a single SG bridges isolated regions. B1, H1. |
There is no low band here: a reachability finding is grounded in the composed graph, not in a heuristic. The uncertainty lives entirely in the boundary (is a host live, is the subnet routed, does a NACL deny, is there app auth), which is why the boundary section is mandatory rather than a footnote.
| Code | Rule | Severity | Grounded in |
|---|---|---|---|
| P1 | Reachable path from the entry to the crown-jewel tier | critical | shortest path in the SG closure |
| B1 | Blast radius spans a lateral hop (distance >= 2) beyond the front door | high | transitive closure from the entry |
| H1 | Pivot/hub SG bridges two or more otherwise-isolated reachable regions | high | articulation point in the reachable subgraph |
The matching half of every rule is the clean verdict: P1 absent (no path), B1 absent (no lateral hop), H1 absent (no bridge) on a segmented fleet is the correct, complete output, not a failure to find something.
The agent's final message in any invocation must include:
Seven end-to-end fixtures are committed under fixtures/, each a fleet of 10-13 security
groups with a runnable replay test. They are split between buried needles and
deceptive-clean fleets, with no short obvious 2-3 hop path in either set:
05-six-hop-cdn-waf-gw-app-svc-db: a
six-hop service chain (CDN to WAF to gateway to app to billing service to db) wired with
ordinary single-upstream references; the P1 needle (critical).06-compromised-ci-runner-deep: the entry
is a compromised CI host, not the internet; the path composes from the foothold inward.07-five-hop-ingress-mesh-broker-db:
a five-hop ingress-to-mesh-to-broker-to-db chain.01-orphaned-front-internal-cidr: the
deep chain exists but the front tier accepts only the internal mesh CIDR, so it is
orphaned from the internet entry. Clean.02-public-alb-no-sg-ref: an intended public ALB
on 0.0.0.0/0 with no onward SG reference. Clean (the public rule is not the headline).03-disjoint-public-vpn-islands: a public
island and an unconnected private island; no route between them. Clean.04-broken-segment-midchain: a deep chain cut
at one mid-chain hop, so it does not reach the crown jewel. Clean.Every fixture has a replay test in tests/ that runs the methodology (via the
deterministic reference engine tests/_reach_engine.py, wrapped by tests/_deep.py)
against the committed JSON, with no external credentials. Run from the skill directory:
for t in tests/replay_*.py; do python "$t" || exit 1; done
The seven tests cover the three needle paths (P1/B1/H1 present, hops correct) and the
four deceptive-clean fleets (no path fabricated). Tests exit non-zero if the audit
composes the wrong path or invents one on a clean fleet. See
tests/README.md for the fixture schema.
This skill is wrong in predictable ways. Read FAILURE_MODES.md
before relying on it. Highlights:
The audit above runs end-to-end against the describe-security-groups +
describe-instances output the user already has. No Anyshift dependency.
Every boundary note in this skill is a join: SG graph to live instance membership, SG graph to the route tables, SG graph to the subnet NACLs, network reachability to the app-layer auth on each tier. The Anyshift MCP can act as a context primer by resolving those joins from a versioned resource graph, so a P1 path can be confirmed (the host is live, the subnet is routed, no NACL denies) instead of left as a hypothesis at the boundary. A measured "with vs without" delta will be published here once the integration has been exercised against the replay fixtures.
npx claudepluginhub anyshift-io/claude-plugins --plugin sre-skillsAssesses network security posture to identify misconfigurations and compliance gaps before production launch or after incidents.
Designs, reviews, and troubleshoots AWS network connectivity including VPCs, Transit Gateway, Direct Connect, VPN, Cloud WAN, Route 53 Resolver, private DNS, CIDRs, route tables, endpoints, segmentation, ingress, egress, inspection, and failover.
Guides AWS VPC network design: subnet tiers (public/private/database), AZ distribution, CIDR ranges, NAT gateways, private endpoints, DNS zones, API gateways.