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.”
| 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. |
Add to mix.exs:
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
]
endRun mix deps.get.
$ mix ex_zip_protect.gen.config # creates config/ex_zip_protect.exs
$ $EDITOR config/ex_zip_protect.exs # edit paths / bucket names / rotationThen in a controller or plug:
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
endDone! Your normal pipeline proceeds unless you explicitly call Bomb.send/2.
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}
]
]| 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. |
%{
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.
ExZipProtect never creates or bundles payloads. A few popular one‑liners:
# 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.brDanger: 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.
| 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 |
[: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.
- 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.
- Streaming upgrades — S3/URL senders are synchronous for now; PRs welcome for fully stream‑chunked variants.
- Custom rotation — Implement the
ExZipProtect.Rotationbehaviour and setrotation: MyModule.
Issues and PRs are welcome! Please run mix test and keep the README.md examples in sync.
ExZipProtect is released under the MIT License.