stableutility included in bundle

image-optimization v0.1.0

On-the-fly JPEG/PNG to WebP/AVIF conversion with content negotiation, filesystem caching, and graceful fallback.

Overview

An image optimization agent for Zentinel that converts JPEG/PNG responses to WebP/AVIF on the fly. Negotiates the best format from the client’s Accept header, converts images via spawn_blocking, and caches results in a content-addressable filesystem store. Falls back to the original image on any error — the agent never makes a response worse.

Features

  • Format Negotiation: Parses Accept header quality values (RFC 7231) to pick WebP or AVIF
  • WebP Conversion: Lossless VP8L encoding via the image crate
  • AVIF Conversion: Lossy encoding with configurable quality via ravif
  • Filesystem Cache: Content-addressable, two-level directory sharding, LRU eviction, configurable TTL
  • Passthrough Patterns: Regex-based URL exclusions (e.g. skip .gif, .svg)
  • Size Guards: Max input bytes and max pixel count to avoid OOM on huge images
  • Graceful Degradation: Decode errors, encode failures, oversized images, and unsupported clients all pass through the original

Installation

# Install just this agent
zentinel bundle install image-optimization

# Or install all available agents
zentinel bundle install --all

Using Cargo

cargo install zentinel-agent-image-optimization

From Source

git clone https://github.com/zentinelproxy/zentinel-agent-image-optimization
cd zentinel-agent-image-optimization
cargo build --release

Configuration

Command Line

zentinel-image-optimization-agent \
    --socket /tmp/image-optimization.sock \
    --log-level info \
    --config /etc/zentinel/image-optimization.json

CLI Options

OptionEnv VarDescriptionDefault
--socketIMAGE_OPT_SOCKETUnix socket path/tmp/image-optimization-agent.sock
--grpcIMAGE_OPT_GRPCgRPC listen address
--configIMAGE_OPT_CONFIGConfiguration file (JSON)
--log-levelIMAGE_OPT_LOG_LEVELLog levelinfo

Zentinel Configuration

agent "image-optimization" {
    transport "unix" {
        path "/tmp/image-optimization.sock"
    }
    events "request_headers" "response_headers" "response_body" "request_complete"
    protocol "v2"
    timeout-ms 5000
    failure-mode "open"

    config {
        "formats" ["webp", "avif"]
        "quality" { "webp" 80; "avif" 70 }
        "max_input_size_bytes" 10485760
        "max_pixel_count" 25000000
        "eligible_content_types" ["image/jpeg", "image/png"]
        "passthrough_patterns" ["\\.gif$", "\\.svg$"]
        "cache" {
            "enabled" true
            "directory" "/var/cache/zentinel/image-optimization"
            "max_size_bytes" 1073741824
            "ttl_secs" 86400
        }
    }
}

Agent Configuration (JSON)

{
  "formats": ["webp", "avif"],
  "quality": { "webp": 80, "avif": 70 },
  "max_input_size_bytes": 10485760,
  "max_pixel_count": 25000000,
  "eligible_content_types": ["image/jpeg", "image/png"],
  "passthrough_patterns": ["\\.gif$", "\\.svg$"],
  "cache": {
    "enabled": true,
    "directory": "/var/cache/zentinel/image-optimization",
    "max_size_bytes": 1073741824,
    "ttl_secs": 86400
  }
}

Options

FieldDefaultDescription
formats["webp", "avif"]Output formats in priority order
quality.webp80WebP quality (1–100)
quality.avif70AVIF quality (1–100)
max_input_size_bytes10485760 (10 MB)Skip images larger than this
max_pixel_count25000000 (25 MP)Skip images with more pixels than this
eligible_content_types["image/jpeg", "image/png"]Response content types to optimize
passthrough_patterns[]Regex patterns for URLs to skip
cache.enabledtrueEnable filesystem cache
cache.directory/var/cache/zentinel/image-optimizationCache root directory
cache.max_size_bytes1073741824 (1 GB)Max total cache size (LRU eviction)
cache.ttl_secs86400 (24 h)Time-to-live for cached entries

How It Works

Client                  Zentinel Proxy              Image Optimization Agent
  │                         │                               │
  │  GET /photo.jpg         │                               │
  │  Accept: image/webp     │                               │
  │────────────────────────►│  request_headers              │
  │                         │──────────────────────────────►│
  │                         │  (extract Accept + URI)       │
  │                         │◄──────────────────────────────│
  │                         │                               │
  │                         │  response_headers             │
  │                         │──────────────────────────────►│
  │                         │  (check content-type,         │
  │                         │   negotiate format,           │
  │                         │   check cache)                │
  │                         │◄──────────────────────────────│
  │                         │                               │
  │                         │  response_body (chunks)       │
  │                         │──────────────────────────────►│
  │                         │  (buffer → convert → cache)   │
  │                         │◄──────────────────────────────│
  │                         │                               │
  │  200 OK                 │                               │
  │  Content-Type: image/webp                               │
  │  X-Image-Optimized: webp                                │
  │◄────────────────────────│                               │

Response Headers

On successful conversion, the agent sets these headers:

HeaderValueDescription
Content-Typeimage/webp or image/avifThe optimized format
Content-Length<bytes>Optimized image size
VaryAcceptDownstream caches vary by format
X-Image-Optimizedwebp, avif, or cache-hitWhich format was served
X-Image-Original-Size<bytes>Original image size before conversion

Failure Modes

All failures result in pass-through of the original image:

ScenarioBehavior
Corrupt image / decode errorPass through original
Encode failurePass through original
Image too large (bytes or pixels)Pass through original, skip processing
Client doesn’t support WebP/AVIFPass through original (no conversion)
Cache write failureServe converted image, log error
Cache read failureTreat as miss, convert normally
Config parse errorReject config with 500 (operator error)

The proxy-level failure-mode "open" setting ensures that if the agent process crashes or times out, the original response passes through unmodified.

Cache Layout

The filesystem cache uses content-addressable storage with two-level directory sharding:

/var/cache/zentinel/image-optimization/
├── a3/
│   └── 7f/
│       ├── a37f...c4e2.bin          # Optimized image bytes
│       └── a37f...c4e2.meta.json    # Metadata sidecar
└── b1/
    └── 02/
        ├── b102...9af1.bin
        └── b102...9af1.meta.json

Cache keys are SHA-256 hashes of "{uri}:{format}:{quality}".

Performance

  • Latency: Depends on image size; cached responses <1ms
  • Memory: ~30MB base + buffered image size
  • Cache hit ratio: Typically >90% for static assets
AgentIntegration
TransformRewrite image URLs before optimization
WAFScan uploads before processing
Rate LimitThrottle conversion-heavy requests