$npx -y skills add anyshift-io/sre-skills --skill sg-deceptive-reachability-auditorAudit a fleet of AWS security groups for the multi-hop lateral-movement path that no single ingress rule reveals. Builds a directed reachability graph from the SG-to-SG references (an ingress rule on SG B naming SG A means a host in A can reach B), adds an internet edge for every
| 1 | # sg-deceptive-reachability-auditor |
| 2 | |
| 3 | Reachability-audit skill for a fleet of AWS security groups. Takes the |
| 4 | `describe-security-groups` output for the fleet (plus `describe-instances` for the |
| 5 | instance-to-SG membership), composes the SG-to-SG references into a directed graph, |
| 6 | and answers one question a per-rule read cannot: from a named entry point, what can |
| 7 | actually be reached, and does any path reach the crown-jewel tier. It returns the |
| 8 | shortest path, the blast radius, and any pivot hub, ranked by severity, then names |
| 9 | exactly where the SG graph stops being able to answer the question. |
| 10 | |
| 11 | The job is a graph problem, and the whole point of the skill is the composition the |
| 12 | graph makes visible. A per-rule read sees "app accepts from web" and "db accepts from |
| 13 | app" as two individually fine rules and never assembles that `internet -> web -> app |
| 14 | -> db` is one reachable path. In a fleet of 10-13 security groups, that chain is buried |
| 15 | among scoped tiers (bastion, monitoring, ci, ssm) and app-fed leaves (cache, queue, |
| 16 | logs), and the loud `0.0.0.0/0` rule on the front door draws the eye away from it. This |
| 17 | skill composes the edges instead of clearing each rule in isolation. |
| 18 | |
| 19 | ## When to invoke |
| 20 | |
| 21 | - An agent is asked to review a security-group fleet for lateral movement, blast |
| 22 | radius, or "can the internet reach the database." |
| 23 | - A fleet is being shipped or changed and the question is whether a low-trust tier can |
| 24 | reach a sensitive one transitively, not just directly. |
| 25 | - An incident assumes a host is compromised and the question is what that foothold can |
| 26 | pivot to. |
| 27 | - A fleet *looks* segmented and the claim "the database is isolated" needs to be |
| 28 | confirmed against the actual edges rather than taken on trust. |
| 29 | |
| 30 | ## What this skill reads, and what it does not |
| 31 | |
| 32 | It reads the static configuration of a **fleet of security groups** plus the |
| 33 | **instance-to-SG membership**. Both are EC2 control-plane reads |
| 34 | (`describe-security-groups`, `describe-instances`). That is the entire input. The audit |
| 35 | is correct and complete *for what the SG graph can tell you*, and it is explicit about |
| 36 | the rest. Reachability-on-paper is not exploitability, and every audit ends by naming |
| 37 | the joins it cannot make: |
| 38 | |
| 39 | - It does **not** confirm a live host is listening. An edge means an SG accepts the |
| 40 | referenced SG; it does not mean an instance in that SG is running and serving. An |
| 41 | empty SG is a path to nothing. Join: SG graph to the live ENIs/instances in each SG. |
| 42 | - It does **not** read route tables. Two SGs on unrouted subnets are not on a routable |
| 43 | path no matter what the ingress rules allow. Join: SG graph to the subnet route tables. |
| 44 | - It does **not** read network ACLs. A NACL is a stateless layer below security groups |
| 45 | and can deny traffic the SG graph would allow. Join: SG graph to the subnet NACLs. |
| 46 | - It does **not** read app-layer auth. A database password, an mTLS handshake, or an |
| 47 | app token can stop a network-reachable hop from becoming access. Join: network |
| 48 | reachability to the app-layer auth on each tier. |
| 49 | |
| 50 | Every audit ends by naming these. A clean (segmented) fleet still gets a boundary |
| 51 | section, because a network-segmented fleet is not a proven-safe system. |
| 52 | |
| 53 | ## The model |
| 54 | |
| 55 | Build a **directed graph over security groups**. An edge `A -> B` exists when SG B has |
| 56 | an **ingress** rule whose `UserIdGroupPairs` includes SG A (B accepts traffic *from* A), |
| 57 | meaning a host in A can reach a host in B. A synthetic node `internet` has an edge |
| 58 | `internet -> X` for every SG X with a `0.0.0.0/0` (or `::/0`) ingress rule. |
| 59 | |
| 60 | From the entry point named for the audit (`internet`, or a compromised instance id that |
| 61 | resolves to that instance's SGs), compute the transitive closure with BFS. A visited set |
| 62 | makes cycles terminate. The findings fall out of the closure. |
| 63 | |
| 64 | ## The methodology, in order |
| 65 | |
| 66 | ### 1. |