A security-hardening and reliability release. It tightens defaults (SSRF protection on,
datastore secrets required, least-privilege webhook reads), closes a server-side
request-forgery vector on media fetches and webhook deliveries, adds an optional Prometheus
metrics endpoint, fixes headless Chromium startup in the non-root Docker image, and refreshes
dependencies. Please read the Upgrade notes below before upgrading from 0.2.1 — several
defaults changed.
Added
- Prometheus metrics at
GET /api/metrics(session/message gauges, process stats).
Disabled by default; setMETRICS_TOKENand scrape withAuthorization: Bearer <token>.
Security
- Webhook secrets no longer leak: the HMAC
secretand customheadersare never
returned from any webhook API response (responses are mapped through a scoped DTO). - Media-fetch SSRF closed: server-side
MessageMedia.fromUrlnow runs an SSRF host
guard + byte cap + timeout before fetching a caller-supplied URL. - Redirects are not followed on webhook deliveries or media fetches, so a
302to an
internal host can't bypass the SSRF guard. - Webhook SSRF protection is ON by default and validated at registration.
- Docker hardening: the socket-proxy is isolated on an
internal: truenetwork reachable
only by the API (not the dashboard); the API container runs withcap_drop: [ALL](+ a
minimal re-add),no-new-privileges, aread_onlyrootfs + tmpfs, and pid/mem limits. - Plugin loader rejects a manifest
mainthat escapes the plugin directory before
require(). - WebSocket: the API key is re-validated on every subscribe (a revoked key is
disconnected), is no longer sent in the handshake URL, and CORS uses the configured
allowlist instead of*. - Production boot guard: the app refuses to start in production with empty/placeholder
secrets, and the committed default datastore credentials were removed. - Rate limiting now keys on the resolved client IP instead of the proxy IP.
Changed
- Webhook read routes now require an
OPERATOR+ key. - Webhook
events[]are validated against the known event types (plus*). - The six inline-body message endpoints (+ label/channel) now validate their input.
- The
mainauth/audit DBsynchronizeis config-driven (MAIN_DATABASE_SYNCHRONIZE,
default on) with a bundled migration forapi_keys/audit_logs. - The readiness probe (
/api/health/ready) now performs real database checks and returns
503 when a dependency is down or the app is draining; the containerHEALTHCHECKpoints
at it.
Fixed
- Message ack status UPDATE is scoped by
sessionId(no cross-session corruption) and
backed by a composite index. getMessagessanitizeslimit/offsetso?limit=abcno longer reaches the query.- The Postgres database name now honors
DATABASE_NAMEconsistently between the runtime and
the migration CLI. - Backup/restore scripts (
scripts/backup.sh/restore.sh) capture both databases
(incl. the auth DBmain.sqlite) + sessions, so a restore preserves API keys. - Boot-time environment validation rejects an unknown
DATABASE_TYPEand missing Postgres
credentials instead of silently coercing. - Message-event idempotency keys are session-scoped.
- Response-envelope documentation corrected to the real raw-payload shape; the unused
interceptor/filter were removed; horizontal-scaling docs marked single-instance. - Headless Chromium now starts in the Docker image as the non-root
openwauser —HOME
points at a writable directory, so the engine no longer dies with
chrome_crashpad_handler: --database is requiredon a fresh container. (closes #242) - Marking a 1:1 chat as read now accepts the newer
@lid(privacy Linked ID) JID, not just
@c.us. Thanks @suraj7974 (#241). - Allowlisted IPv6 literals in
SSRF_ALLOWED_HOSTSnow match whether or not the entry is
bracketed (e.g.[::1]and::1). - The dashboard returns cleanly to the login screen on a
401instead of flashing a transient
error toast. - A webhook
secretcleared via update is normalized to "no secret" (consistent with create)
and is length-capped.
Dependencies
@bull-board/{api,nestjs,express}7.2.1 → 8.0.0 and@types/archiver7 → 8 (aligned with the
archiver v8 runtime), plus a batch of minor/patch bumps (NestJS 11.1.27, BullMQ 5.78.1, AWS SDK,
ESLint 10.5, Prettier 3.8, typescript-eslint 8.61, and a dashboard dev-tool bump).
Upgrade notes (behavior changes)
- Webhook reads now require
OPERATOR+ — aVIEWERkey reading webhooks gets403. - SSRF protection defaults ON — deployments that deliver webhooks or fetch media from
internal hosts must setSSRF_ALLOWED_HOSTS(comma-separated) orWEBHOOK_SSRF_PROTECT=false. - Datastore secrets are now required — there is no
openwa/minioadmindefault;
docker compose --profile postgres/minio upneedsDATABASE_PASSWORD/S3_*set, and
production refuses to boot with placeholder secrets. - Bull Board
?apiKey=removed — authenticate viaX-API-Key/Authorization: Bearer. - New env knobs:
SSRF_ALLOWED_HOSTS,MEDIA_DOWNLOAD_MAX_BYTES,MEDIA_DOWNLOAD_TIMEOUT_MS,
MAIN_DATABASE_SYNCHRONIZE,SHUTDOWN_DELAY_MS,OPENWA_MEM_LIMIT,METRICS_TOKEN.