> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anyshift.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Origin reachability

> Read paths[].originReachability on public exposure results to see whether an ALB or NLB origin is reachable without Cloudflare.

When a public exposure path ends at an AWS load balancer, each path can include `originReachability`. Use it to check whether the origin itself accepts traffic from outside Cloudflare, based on security-group ingress compared to pinned Cloudflare IP ranges.

This is origin-control evidence on the path. It does not change the overall exposure verdict, and it is not a traffic-path failure.

## Try it

```bash theme={null}
annie graph exposure api.example.com --type CLOUDFLARE_HOSTNAME
annie graph exposure api.example.com --type CLOUDFLARE_HOSTNAME --output json \
  | jq '.data.exposure.paths[] | {verdict: .originReachability.verdict, reasons: .originReachability.reasons}'
```

Or with Graph Query Language:

```console theme={null}
$ annie graph query "SELECT * FROM exposure WHERE resource = api.example.com"
```

The same field is available from `graph.exposure()` in the [Graph SDK](/pages/product/integration/sdk_capabilities#security-and-exposure) and from the Graph MCP `get_exposure` tool.

## Field shape

`paths[].originReachability` is either an object or JSON `null`.

`null` means the path was not evaluated for origin reachability (for example, no ALB or NLB origin was present on that path).

When present:

```json theme={null}
{
  "verdict": "restricted_to_cloudflare",
  "reasons": [
    "all advertised address families have Cloudflare-only ingress on evaluated listeners"
  ],
  "advertisedFamilies": ["ipv4", "ipv6"],
  "coveringRules": [
    {
      "family": "ipv4",
      "cidr": "173.245.48.0/20",
      "groupId": "sg-abc",
      "permissionIndex": "0",
      "fromPort": 443,
      "toPort": 443,
      "protocol": "tcp",
      "coveredByCloudflare": true,
      "worldOpen": false
    }
  ],
  "uncoveredRules": [],
  "missingEvidence": [],
  "cfRanges": {
    "version": "20260818",
    "publishedAt": "2026-08-18T00:00:00.000Z",
    "source": "https://www.cloudflare.com/ips/"
  }
}
```

| Field                | Meaning                                                        |
| -------------------- | -------------------------------------------------------------- |
| `verdict`            | Origin-control conclusion for this path                        |
| `reasons`            | Human-readable explanation of the verdict                      |
| `advertisedFamilies` | Address families evaluated (`ipv4`, `ipv6`)                    |
| `coveringRules`      | Ingress rules covered by pinned Cloudflare ranges              |
| `uncoveredRules`     | Ingress rules that are world-open or outside Cloudflare ranges |
| `missingEvidence`    | Inventory pieces that blocked a definitive restricted verdict  |
| `cfRanges`           | Versioned Cloudflare IP range pin used for the comparison      |

## Verdicts

| Verdict                    | Meaning                                                                                                        |
| -------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `restricted_to_cloudflare` | Every advertised address family has complete Cloudflare-only security-group ingress on the evaluated listeners |
| `directly_reachable`       | At least one world-open or non-Cloudflare ingress CIDR can reach the origin                                    |
| `unknown`                  | Evidence is incomplete, stale, partial across address families, or otherwise insufficient                      |
| `not_applicable`           | The origin shape is out of evaluated scope for this path                                                       |

Incomplete evidence never becomes `restricted_to_cloudflare`. Prefer reading `unknown` plus `missingEvidence` when inventory is partial.

## What this is not

`originReachability` answers a different question from DNS proxy state:

| Signal                                    | Answers                                                                                                                                            |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cloudflare DNS `proxied` (orange cloud)   | Whether Cloudflare edge is configured to terminate DNS for the hostname                                                                            |
| Graph relationship `PROXIES_TO`           | Stored proxied hostname → origin relationship                                                                                                      |
| Graph relationship `RESOLVES_DIRECTLY_TO` | DNS-only bypass path (`proxied=false`) to a reviewed terminal                                                                                      |
| `paths[].originReachability`              | Whether the public ALB or NLB origin itself is reachable without Cloudflare, from load-balancer security-group ingress vs pinned Cloudflare ranges |

A hostname can be orange-cloud (`PROXIES_TO`) and still be `directly_reachable` at the origin if the security group allows `0.0.0.0/0`. Conversely, `restricted_to_cloudflare` does not mark the exposure path as failed.

Annie CLI prints origin reachability separately from traffic gaps so it never looks like a missing hop.

## Supported origins

Evaluated today:

* Internet-facing AWS Application Load Balancer with security-group ingress
* Internet-facing AWS Network Load Balancer with attached security groups

Still `not_applicable` or `unknown` without enough preservation evidence:

* NLB shapes without attached security groups, or target-SG-only analysis
* Gateway Load Balancer
* Kubernetes NetworkPolicy source-IP analysis (needs observed client-IP preservation)

Connect [Cloudflare](/pages/integration/cloudflare) and AWS inventory so exposure paths and security-group evidence are present before you treat a restricted verdict as conclusive.

## Related surfaces

* [Annie CLI](/pages/product/integration/cli): `annie graph exposure`
* [Graph MCP](/pages/product/integration/graph_mcp): `get_exposure`
* [Graph Query Language](/pages/product/integration/graph_query_language#exposure): `SELECT * FROM exposure ...`
* [SDK capabilities](/pages/product/integration/sdk_capabilities#security-and-exposure): `graph.exposure()`
