Files
ihasmail-inbuxa/.env.example
T
jcoffey-dev f1a2972d3a Harden four things the audit turned up
**The login rate limiter could be sidestepped.** X-Forwarded-For is a list each
hop appends to, and nginx's $proxy_add_x_forwarded_for appends ours — so a
client sending "X-Forwarded-For: 1.2.3.4" arrives as "1.2.3.4, <their real
address>". Reading the leftmost entry, as we did, handed the caller a
rate-limit key they could change per request: unlimited password guessing
against a deployment that looks correctly configured. Read from the right
instead, skip hops that are themselves trusted proxies, and believe the header
only when the peer is one (loopback and the private ranges by default,
TRUSTED_PROXIES to be explicit).

**The upload cap was a suggestion.** It read content-length, which a chunked
request simply omits. Count the bytes through a stream, as the image proxy
already does.

**App password secrets were drawn with a modulo.** 256 is not a multiple of 33,
so the first 25 characters of the alphabet came up on 8 byte values and the
last 8 on only 7. Rejection sampling instead. The test weighs the whole tail of
the alphabet rather than single characters, because a 7/8 skew is invisible
per character against the noise — and it does fail when the bias is put back.

**Upstream headers were relayed wholesale.** Anything the mail server set —
cookies, auth challenges, CORS grants — landed on our origin, where it means
something else. Allowlist what is actually wanted.
2026-08-24 11:10:15 -07:00

42 lines
1.4 KiB
Bash

# ---- ihasmail server configuration ----
# Base URL of your Stalwart server (scheme + host, no path). ihasmail discovers
# the JMAP session at <STALWART_URL>/.well-known/jmap.
STALWART_URL=https://mail.example.com
# Random secret used to derive encryption keys for persisted sessions.
# Generate with: openssl rand -base64 48
APP_SECRET=change-me
# Listen address
HOST=0.0.0.0
PORT=8080
# Set to "1" when running behind a TLS-terminating reverse proxy (trusts
# X-Forwarded-* and marks cookies Secure). Set to "0" for plain-HTTP dev.
TRUST_PROXY=1
# Peers whose X-Forwarded-* headers are believed. Unset means loopback and the
# private ranges, which covers a reverse proxy on the same host or Docker
# network. A request from anywhere else is attributed to its socket address,
# whatever the headers claim -- otherwise anyone could pick their own key for
# the login rate limiter.
# TRUSTED_PROXIES=10.0.0.0/8,192.168.1.5
SECURE_COOKIES=auto
# Session lifetime (idle timeout) in seconds. "Remember me" extends to SESSION_REMEMBER_TTL.
SESSION_TTL=43200
SESSION_REMEMBER_TTL=2592000
# Where to persist sessions so restarts don't log everyone out (optional).
SESSION_FILE=./data/sessions.json
# Upstream timeouts / limits
UPSTREAM_TIMEOUT=30000
MAX_UPLOAD_BYTES=52428800
# Remote-image privacy proxy (Gmail-style). Set to 0 to load remote images directly.
IMAGE_PROXY=1
# Branding
APP_NAME=ihasmail