# ExZipProtect
[](https://hex.pm/packages/ex_zip_protect)
[](https://hexdocs.pm/ex_zip_protect/)
[](https://hex.pm/packages/ex_zip_protect)
[](https://hex.pm/packages/ex_zip_protect)
[](https://github.com/houllette/ex_zip_protect/commits/main)
Protect your Phoenix/Plug applications from low‑effort scrapers, scanners, and spam bots by serving them **compressed "zip bombs"**—ultra‑small payloads that expand to hundreds of MB/GB and exhaust their memory.
ExZipProtect does **zero detection itself**; it simply makes it trivial for *you* to return a pre‑built bomb when your own heuristics say, “Nuke this request.”
---
## ✨ Features
| Feature | Details |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| **BYOB** | *Bring Your Own Bomb*—the library never ships with or generates payloads. You point to files you already built (gzip / zstd / brotli, etc.). |
| **Multiple storage back‑ends** | `{:file, path}` · `{:s3, bucket: …, key: …}` · `{:url, "https://…"}` |
| **Rotation (opt‑in)** | `:none` (default), `:random`, or `:round_robin` across multiple bombs per severity level. |
| **Staging safe‑guard** | Entire library can be disabled with a single flag (default off in non‑`prod` envs). |
| **Enable bypass header** | Configure an optional HTTP header to serve as bypass to ExZipProtect (useful for security scanners). |
| **Telemetry audit event** | Emits `[:ex_zip_protect, :bomb, :served]` so you can track activations. |
| **Mix generator** | `mix ex_zip_protect.gen.config` scaffolds a ready‑to‑edit config file. |
---
## 📦 Installation
Add to `mix.exs`:
```elixir
defp deps do
[
{:ex_zip_protect, "~> 0.1"},
# OPTIONAL — required only if you use these source types:
{:ex_aws, "~> 2.5", optional: true}
{:ex_aws_s3, "~> 2.5", optional: true}, # for :s3 sources
{:finch, "~> 0.18", optional: true} # for :url streaming if not already in deps
]
end
```
Run `mix deps.get`.
---
## 🔧 Quick start
```bash
$ mix ex_zip_protect.gen.config # creates config/ex_zip_protect.exs
$ $EDITOR config/ex_zip_protect.exs # edit paths / bucket names / rotation
```
Then in a controller or plug:
```elixir
alias ExZipProtect.Plug, as: Bomb
plug :maybe_bomb
defp maybe_bomb(conn, _opts) do
case Detection.classify(conn) do
{:bomb, :low } -> Bomb.send(conn, :low)
{:bomb, :medium} -> Bomb.send(conn, :medium)
{:bomb, :high } -> Bomb.send(conn, :high)
_other -> conn
end
end
```
Done! Your normal pipeline proceeds unless you explicitly call `Bomb.send/2`.
---
## ⚙️ Configuration reference (`config/*.exs`)
```elixir
import Config
config :ex_zip_protect,
enabled?: config_env() == :prod, # disable library outside prod by default
rotation: :none, # :none | :random | :round_robin
levels: [
low: [ %{source: {:file, "/srv/bombs/low.gz"}, encoding: :gzip} ],
medium: [ %{source: {:s3, bucket: "bombs", key: "5mb.zst"}, encoding: :zstd} ],
high: [
%{source: {:url, "https://cdn.example.com/10mb.br"}, encoding: :br},
%{source: {:url, "https://cdn2.example.com/20mb.br"}, encoding: :br}
]
]
```
### Keys
| Key | Type | Default | Notes |
| ----------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `enabled?` | bool | `true` | When `false`, `Bomb.send/2` is a no‑op. |
| `rotation` | atom | `:none` | Distribution strategy across a list of bombs for the same level. |
| `levels` | keyword | — | Map severity `level => [bomb_spec, …]`. |
| `bypass_header` | string | nil | If set, _any_ request containing that header bypasses bombs. Leave `nil` (or comment out) to disable the feature. |
### Bomb spec
```elixir
%{
source: {:file, "/path"} | {:s3, bucket: "…", key: "…", opts: [...] } | {:url, "https://…"},
encoding: :gzip | :zstd | :br | :deflate | atom(),
bytes: 1_000_000 # optional – sets Content‑Length without stat
}
```
*If `:bytes` is absent*, ExZipProtect tries to derive size via `File.stat/2`, `HEAD` request, or S3 object metadata.
---
## 💣 Bring Your Own Bomb (BYOB)
ExZipProtect **never** creates or bundles payloads. A few popular one‑liners:
```bash
# 1GB inflate → 1MB gzip
$ dd if=/dev/zero bs=1G count=1 | gzip -c > 1mb-1gb.gz
# 10GB inflate → 10MB zstd (fast)
$ dd if=/dev/zero bs=1G count=10 | zstd -19 -o 10mb-10gb.zst
# 50GB inflate → 10MB brotli (aggressive)
$ dd if=/dev/zero bs=50G count=1 | brotli -q11 -o 10mb-50gb.br
```
> **Danger:** Decompressing these files locally may hang or crash your machine. Build them in a throwaway container or server.
Upload the resulting file to your chosen storage, then reference it in `config`.
---
## 🚚 Where to host bombs?
| Source | `source:` tuple | Pros | Cons |
| -------------------- | ------------------------------------- | ----------------------- | -------------------------------------- |
| **Local file** | `{:file, "/srv/bombs/…"}` | Fast, no network | Consumes disk on every app node |
| **S3 / GCS / MinIO** | `{:s3, bucket: "…", key: "…"}` | Offloads storage; cheap | Requires `ex_aws_s3` dep & network I/O |
| **HTTPS URL (CDN)** | `{:url, "https://cdn.example.com/…"}` | Global edge caching | Adds latency; ensure CORS/ACL OK |
---
## 📈 Telemetry
`[:ex_zip_protect, :bomb, :served]`
| Measurements | Metadata |
| ------------------------------- | -------------------------- |
| `:bytes` – size (or `:unknown`) | `:level`, `:ip`, `:source` |
The application supervisor attaches a default `Logger.warning/1` handler—you can detach it and forward to Honeycomb, Datadog, OpenTelemetry, etc.
---
## 🛡️ Legal & ethical notes
* Serving bomb files is a form of denial‑of‑service **against the client**. Ensure this practice is allowed by your provider & jurisdiction.
* Provide a bypass header (e.g. `X‑ZipBomb: false`) if you need certain security scanners to skip bombs.
* Never serve bombs accidentally in dev/staging—leave `enabled?: config_env() == :prod`.
---
## 🛠️ Advanced topics
* **Streaming upgrades** — S3/URL senders are synchronous for now; PRs welcome for fully stream‑chunked variants.
* **Custom rotation** — Implement the `ExZipProtect.Rotation` behaviour and set `rotation: MyModule`.
---
## 🤝 Contributing
Issues and PRs are welcome! Please run `mix test` and keep the `README.md` examples in sync.
---
## 📜 License
ExZipProtect is released under the MIT License.