# `ExSandbox.Egress.Policy`
[🔗](https://github.com/FoundryStack/ex_sandbox/blob/v1.2.0/lib/ex_sandbox/egress/policy.ex#L1)

Which destinations one sandbox may reach, and how that sandbox is identified
(005 T060a1/T060a2, `contracts/egress.md`, `005-FR-011a`–`FR-011e`).

## Identity is the source address, and it is unforgeable

A sandbox's policy is keyed by the **source /30 its packets arrive from**.
That address is the kernel's account of which network namespace a connection
originated in — `:inet.peername/1` on the accepted socket, not anything the
connecting party said.

This matters more than it looks. Tenant code cannot claim another sandbox's
allowlist, because it never asserts an identity at all: there is no token to
steal, no header to spoof, and no name to collide with. The strongest form of
an authorization check is one where the subject cannot participate in
answering it.

⚠️ That property depends on **no sandbox having a route to any other**
(`005-FR-011c`, `003-FR-002`). If two sandboxes shared a link, one could
originate a connection from within the other's /30 and inherit its policy.
This is why `ExSandbox.SharedRouteMechanism` exists as an adversary: it is
conformant in every outward-facing respect and fails only that, so a check
that trusts topology instead of attempting a crossing shows up as a false
pass.

## Default deny

`permits?/2` answers `false` for a /30 with no entry. A missing policy is not
an absent restriction — it is the most restrictive one. `FR-011a` requires an
allowlist *over* default-deny, and the ordering is the whole guarantee: a
lookup miss must never be the path by which something becomes reachable.

# `destination`

```elixir
@type destination() :: {ip() | String.t(), :inet.port_number() | :any_port}
```

A permitted destination. `:any_port` admits a host on every port; a specific
port admits only that one.

# `ip`

```elixir
@type ip() :: :inet.ip4_address()
```

An IPv4 address as `:inet` reports it.

# `resolutions`

```elixir
@type resolutions() :: %{optional(String.t()) =&gt; MapSet.t(:inet.ip_address())}
```

What one sandbox resolved, per name (`029-FR-012`).

⚠️ Per **sandbox**, never global. A shared table would let one tenant's
resolution of a name decide another tenant's verdict for it.

# `source_key`

```elixir
@type source_key() :: ip()
```

The /30 a sandbox's connections originate from, as `{a, b, c, d}`.

# `normalise_name`

```elixir
@spec normalise_name(String.t()) :: String.t()
```

The canonical form of a DNS name for comparison: lower-cased, with any
trailing root dot removed.

Public because the **recording** side and the **matching** side must agree
exactly, and two copies of this would be two things that must stay equal
forever. The symptom of them drifting is a permitted host that is silently
refused, which reads as an unreachable network.

# `permits?`

```elixir
@spec permits?(
  [destination()],
  {ip() | String.t(), :inet.port_number()},
  resolutions()
) :: boolean()
```

True only when `destination` is explicitly permitted for `source`.

Returns `false` for an unknown source — see the default-deny note above.

`resolutions` is what **this sandbox** resolved, as
`ExSandbox.Egress.Registry.resolutions/2` returns it: `%{name => MapSet of
addresses}`. It is what makes a hostname entry able to match at all
(`029-FR-012`); omitted, it defaults to `%{}` and no hostname entry matches
anything, which is the pre-`029` behaviour and is default-deny.

# `source_key`

```elixir
@spec source_key(ip()) :: source_key()
```

Reduces a source address to the /30 it belongs to.

⚠️ Masking is what makes the key stable. A sandbox's connections come from a
*host* address inside its /30, and that address is not necessarily the network
address — so keying on the raw source would miss. Masking to the /30 answers
"which sandbox is this?" rather than "which address is this?".

---

*Consult [api-reference.md](api-reference.md) for complete listing*
