Repository navigation
Releases: shuaiZend/PingWAF
Release list
v0.32.2
Security and robustness release. One new table (refresh_tokens) ships with
the automatic migration; /api/v1/version no longer exposes deployment posture.
Security
- Unauthenticated control-plane crash fixed. An API key whose prefix was
sliced on a non-UTF-8 boundary panicked; release builds abort on panic, so a
singleRegisterAgentcall could stop the process. The prefix is now
ASCII-checked and sliced withget. - Cross-tenant log read fixed. Listing logs with an explicit site id now
validates site ownership, as every other site-scoped handler already did. - WAF request-body coverage. Unknown
Content-Encoding(br/zstd/chained)
is reported as uninspected instead of silently scanning compressed bytes;
UTF-16 bodies are transcoded before scanning; multipart text parts are
inspected as fields; h2/h3 GET/HEAD bodies are staged lazily. - Body staging is bounded and observable. Per-site quotas, deadline-driven
sweep with a fail-closed tombstone,body_prefix_only/
body_encoded_uninspected/body_expanded_capevents, and opt-in
body_stage_policy=strict(413/408) /encoded_body_policy=reject(415). - bcrypt is offloaded and capped, and
/mcpplus gRPCRegisterAgentare
rate-limited per peer. - Refresh tokens are single-use. Every refresh token carries a
jti; the
newrefresh_tokenstable makes replay fail across restarts and instances. - Smaller hardening: control-plane WAF skips large upload bodies (no more
413 on GeoIP/backup upload), API allowlist keeps its last known-good set when
a referenced group disappears, access-log query strings redact known secrets,
and configuration rollback is serialised with an advisory lock.
Fixed
- Heartbeats cap
blocked_ips(4096, with ablocked_ips_truncatedflag) so a
large block list cannot exceed the gRPC frame limit and wedge the link;
truncation no longer revokes unseen rows. - Agent id is resolved once (empty config no longer mints a new UUID per call).
- cgroup memory pressure no longer falls back to host-wide free memory.
- Domain index removal only deletes entries still owned by the site.
- Custom WAF rules that fail to compile are logged instead of dropped silently.
- Config files are written atomically with mode 0600; header size limits count
bytes; least-connections counters recover from a poisoned lock. - Cache keys include the request host; circuit breakers return to OPEN when a
half-open probe is never answered; responses with cookies and no freshness
are not cached viamax_ttl. - CLI:
config history/show/rollbackare routed to maintenance mode; registry
lists are pinned to clap; unknown config categories return 400. - Release: tag/version mismatch aborts before the release is created;
install.shhonoursPINGWAF_MODE/PINGWAF_DB_URL; the runtime image's
healthcheck handles agent mode;publish.shstops on the first failure.
Changed
GET /api/v1/versionno longer returnsgrpc_tls. The flag moved to the
admin-onlyGET /api/v1/system/status, and the console fetches it there.- New migration
refresh_tokens.
Full Changelog: v0.0.0...v0.32.2
v0.32.1
Patch release focused on WAF memory amplification and control-plane write
amplification. No API, proto or database schema changes.
Fixed
- WAF disabled/off sites no longer compile an engine.
SiteContextonly
builds the per-site engine whenenabledis true andmodeis notoff;
off sites still run IP/geo/mTLS/basic-auth/bot/rate-limit controls and keep
their access-log body prefix. - Site contexts are shared per site id. Alternate/wildcard hosts now point
at the sameArc<SiteContext>; cache invalidation uses the immutable
Arc<SiteRules>identity instead of the global config hash. The by-id table
has its own TTL/entry sweep. - Regex compilation is bounded and cached. Parsed expressions are cached
by expression text (512 entries; no id/name key), and regex construction has
a 4MiB size/DFA limit with fallback to the previous builder. - Heartbeat certificate writes are coalesced. A heartbeat reads current
certificate rows once and only updates sites whose status or expiry changed,
instead of issuing one UPDATE per site per heartbeat. - SQLx pool acquisition is bounded. Requests now fail within 5s when the
pool is saturated instead of waiting out the 30s default.
Full Changelog: v0.0.0...v0.32.1
v0.32.0
Major update: the control-plane listener, WAF resource bounds, site-level
timeouts and the dashboard ranking surfaces all move together in this release.
Added
- Per-site downstream timeout. Site → Basic configures a 1–3600s read/write
budget (default 60s), persisted on the site and delivered over proto tag 11.
It applies after the Location matches; HTTP/1.1 keep-alive restores the
listener default before the next request's headers, and HTTP/2 write budgets
are per stream. - Shared dashboard ranking tables. The dashboard's top threat IPs/rules and
the traffic page now use one table implementation with a window-aware log
drill-down, loading/empty/error states and a consistent action tone map. - Response replacement on the Rewrite Rules page. The policy moved next to
the other response-rewriting controls; the stored field and API are unchanged.
Fixed
- Control-plane admin listener cannot be wedged by silent clients.
MixedListenernow accepts in a dedicated task and performs protocol
detection/TLS handshakes in bounded per-connection tasks, with 5s peek, 10s
TLS handshake, 128 pre-handshakes, 512 active connections, a 60s idle
deadline and a reserved loopback pool. Health checks and readiness reporting
are bounded as well. - WAF resource bounds and normalization cost. Expression depth/token/length
limits, bounded query/JSON/decompression surfaces, multipart field scanning,
ACME token validation,max_body_size/threshold clamps and per-request
Cownormalization reduce both crash and memory-amplification risk. - Heartbeat persistence. Added migration 082 indexes, hourly certificate
sweep throttling, empty blocked-IP fast deletion and monotonic conditional
mark_offline, so a stale heartbeat cannot overwrite a newer one. - Proxy safety defaults. Public listeners get 60s downstream timeouts and
keepalive/H2 bounds, generated locations get a 16MB body cap, upstream
certificate verification defaults to strict, and retries are capped at three
for idempotent methods only.
Full Changelog: v0.0.0...v0.32.0
v0.31.1
Patch release for ACME issuance reliability and repeatable browser E2E coverage.
No schema or API breaking changes; 0.31 and latest move to this patch.
Added
- Browser E2E suite. Playwright scenarios cover site creation, routing and
rewrites, multi-origin/health behavior, WebSocket, large/streamed bodies,
cache hits, gzip, upstream TLS, path traversal, host mismatch, uploads and
ACME staging. Tests require explicit demo/origin/domain inputs and never fall
back to a shared production hostname. - Staging ACME opt-in.
PINGAP_ACME_STAGINGselects the staging directory
and account (1/true/yes/on/staging/stage); production remains the default.
Fixed
- ACME is independent of upstream availability. The issuance task and
port-80 challenge handler stay enabled across hot reloads, and a site with an
ACME certificate but no location keeps its certificate/listeners instead of
dropping the whole data-plane config. - ACME retries are bounded. Failed orders back off 5m → 15m → 30m → 1h → 6h
instead of creating a new order every minute; success clears the state. - ACME flags are consistent.
PINGAP_DISABLE_ACME=false/0/no/offno longer
disables issuance merely because the variable is present. - Failure diagnostics redact URL query strings before they reach the
database, console or notifications. - New orders are attempted on the next one-minute scan rather than waiting
for every tenth tick.
Full Changelog: v0.0.0...v0.31.1
v0.31.0
This release brings the Account page in line with Settings and makes
configuration history a complete, encrypted and restorable safety net.
Added
- Account page parity with Settings. The same sticky quick navigation,
scroll-spy and section layout, filtered by role. OIDC providers and visitor
accounts come first, users next, and API keys are always last. - Visitor remarks. Create, edit, list and clear a console-only note of up to
500 characters for recognising accounts in collaborative operation. Stored by
a new migration and never exposed to the data plane. - Complete encrypted configuration backups. A unified 38-table restorable
catalogue covers site policy, shared resources, OIDC, visitors and grants,
notifications, AI, cache and certificates. Password-based Argon2id and
AES-256-GCM encryption, zstd compression, SHA-256 verification, and bounded
S3-compatible/GitHub upload tasks with expiring leases. Target credentials are
sealed, never returned by the API, and remote storage receives only ciphertext. - Upload preview and safe restore. Decrypt, validate and preview before/after
counts, changes, owner mappings and warnings. Confirmation creates an
encrypted pre-restore backup and applies the replacement in the same
serializable transaction, with runtime refresh warnings reported separately. - Reliable retention. The 30-day and newest-50 rules now run at startup,
daily and on demand, and also apply to backup records. Backup lists read
metadata only, not ciphertext.
Fixed
- Snapshot and rollback correctness. v2 snapshots and v1 readers share one
engine: missing tables are preserved, explicitly empty tables are cleared,
global singleton objects are readable, new columns get database defaults, and
restore uses actual writes rather thanActiveModel'sUnchangedstate. - Review-discovered safety issues. Corrected
jsonb_populate_recordset
argument order, prevented missing-sitespruning, retained complete
configurations for sites outside ordinary rollbacks, bounded untrusted KDF
parameters and concurrency, preserved exact download bytes, re-sealed
instance-managed secrets for the restoring instance, fenced stale upload
attempts and invalidated stale restore previews. - Certificates without a private key restore correctly. An explicit
null
key_pemis a legitimate certificate state, while non-nullable secret columns
and unresolved secret materials still fail closed. - IP-group references validate correctly. Reference ids now use the same raw
string form as database UUIDs instead of their JSON-quoted representation.
Full Changelog: v0.0.0...v0.31.0
v0.31.0-rc.3
Pre-release. Third candidate: rc.2 plus the reference-validation fix from
the end-to-end run. Publishes:0.31.0-rc.3only —:latestand:0.31keep
pointing at the newest stable release. Pin it explicitly:
PINGWAF_TAG=0.31.0-rc.3.
Fixed
- References to existing IP groups validate correctly. UUIDs were compared
in their JSON-quoted form, so a valid group could be reported missing during
preview and restore. Reference ids now use the same raw-string form as the
database ids, and a regression test covers the comparison.
Full Changelog: v0.0.0...v0.31.0-rc.3
v0.31.0-rc.2
Pre-release. Second candidate: rc.1 plus a restore fix found by the
end-to-end run. Publishes:0.31.0-rc.2only —:latestand:0.31keep
pointing at the newest stable release. Pin it explicitly:
PINGWAF_TAG=0.31.0-rc.2.
Fixed
- Certificates with no private key can be restored. The secret preflight
treated an explicitnullkey_pemas an attempt to bypass secret recovery,
but that is a legitimate state on certificate tables. Preview and restore now
accept it, while non-nullable secret columns and unresolved secret materials
still fail closed.
Full Changelog: v0.0.0...v0.31.0-rc.2
v0.31.0-rc.1
Pre-release. First candidate for the account-page and configuration-backup
work. Publishes:0.31.0-rc.1only —:latestand:0.31keep pointing at
the newest stable release, and GitHub marks it a pre-release soinstall.sh
(which resolves/releases/latest) is unaffected. Pin it explicitly:
PINGWAF_TAG=0.31.0-rc.1.
Added
- Account page parity with Settings. The same sticky quick navigation,
scroll-spy and section layout, filtered by role. OIDC providers and visitor
accounts come first, users next, and API keys are always last. - Visitor remarks. Create, edit, list and clear a console-only note of up to
500 characters for recognising accounts in collaborative operation. Stored by
a new migration and never exposed to the data plane. - Complete encrypted configuration backups. A unified 38-table restorable
catalogue covers site policy, shared resources, OIDC, visitors and grants,
notifications, AI, cache and certificates. Password-based Argon2id and
AES-256-GCM encryption, zstd compression, SHA-256 verification, and bounded
S3/GitHub upload tasks with expiring leases. Target credentials are sealed,
never returned by the API, and remote storage receives only ciphertext. - Upload preview and safe restore. Decrypt, validate and preview before/after
counts, changes, owner mappings and warnings. Confirmation creates an
encrypted pre-restore backup and applies the replacement in the same
serializable transaction, with runtime refresh warnings reported separately. - Reliable retention. The 30-day and newest-50 rules now run at startup,
daily and on demand, and also apply to backup records. Backup lists read
metadata only, not ciphertext.
Fixed
- Snapshot and rollback correctness. v2 snapshots and v1 readers share one
engine: missing tables are preserved, explicitly empty tables are cleared,
global singleton objects are readable, new columns get database defaults, and
restore uses actual writes rather thanActiveModel'sUnchangedstate. - Review-discovered safety issues. Corrected
jsonb_populate_recordset
argument order, prevented missing-sitespruning, retained complete
configurations for sites outside ordinary rollbacks, bounded untrusted KDF
parameters and concurrency, preserved exact download bytes, re-sealed
instance-managed secrets for the restoring instance, fenced stale upload
attempts and invalidated stale restore previews.
Full Changelog: v0.0.0...v0.31.0-rc.1
v0.30.2
Security
-
The WAF's step-aside for the visitor login page covered a prefix, which was
a way around the WAF. 0.30.1 skipped every path starting with/_pingwaf/
so a rule could not block the site's own front door. But a prefix matches
/_pingwaf/<anything>: on a site with no login page — where nothing serves
that prefix — the request skipped every rule and went straight upstream, and
the skip sat before the dynamic IP block, so a blocked client could still use
it. Confirmed against a live deployment:/.envand a random path were
blocked, while/_pingwaf/anythingreached the origin./_pingwaf/../admin
matched too, and most origins normalise the..away — a path-traversal hole
rather than just an unprotected one.The skip is now the exact endpoints the plugin owns, and only when the site's
chain actually carries the plugin that serves them. On any other site those
paths are not an endpoint, they are just a path, and a path that skips every
rule is a hole.
Fixed
- The stop record was matched without an upper bound.
stopped_at >= previous.booted_atalone accepts a row written later — by a second process
sharing the hostname, by a restored dump, by a fast clock — as evidence about
a boot it never described, which would label a crash a planned stop. That is
the one direction the restart-history mechanism must not fail in, so the
match is bounded at both ends now.
Full Changelog: v0.0.0...v0.30.2
v0.30.1
Four findings from a full test pass over 0.30.0, three of them latent since the
features they belong to landed.
Fixed
- A planned restart no longer looks like a crash. The control-plane restart
history inferred everything from the gap between two starts, which cannot
tell adocker compose restartfrom akill -9: both are followed by a
start seconds later, so every planned restart was labelleduncleanand an
ordinary redeploy read as a crash loop. A process that is given a chance to
stop can say so, so it now writes a stop row on the way out
(control_plane_stops, migration077) and the next start reads it. The row
is matched against the previous boot's own timestamp rather than merely
existing, so a stop left over from two restarts ago cannot make a crash look
planned. - The restart history's retention bound is per host, not per table. One host
in a restart loop could push another host's rows out of the window, so the
replica that is fine lost the history an operator would read to see that it
is fine. Each host is trimmed to its own window now — also the unit the
console reads in. - A WAF rule could block the visitor login page. The visitor plugin runs
after the WAF, so a rule that matched/_pingwaf/loginanswered the site's
own front door with a block page. Found by driving the flow against a live
deployment: a burst of sign-ins tripped a rule and every later visit to the
login page returned403until the IP was unblocked by hand — a lockout with
no way back in from the site itself. The whole/_pingwaf/surface is now
skipped, because no rule an operator writes should be able to lock them out
of the way back in. The endpoints keep their own protection: a proof-of-work
challenge gates every sign-in. - The demo's 443 handshake failure was not a code fault. The site had no
certificate at all, so the listener fell back to another site's and the
signature algorithm it offered did not suit the client. A certificate for the
site fixes it; TLS 1.3 completes and the site answers.
Full Changelog: v0.0.0...v0.30.1