Skip to content

Releases: 0x00spor3/Light-WAF

Light WAF v0.4.0

Choose a tag to compare

@0x00spor3 0x00spor3 released this 05 Jul 19:08

Light WAF v0.4.0 — Decision-log score breakdown (drill-down data)

A fast, modular Layer-7 Web Application Firewall written in Rust, operating as a
reverse proxy: it inspects every HTTP request, accumulates a CRS-style anomaly score,
and decides Allow / Block (403) / Reject (400 | 429) before forwarding to the backend.

This is a small, additive release focused on observability. There are no changes to
detection
— the corpus verdicts are identical to v0.3.0. The whole workspace is bumped to
0.4.0 together (uniform versioning).

What's new

Per-rule score_contributions in the decision-log (the headline)

When a request is blocked or rejected, the structured decision-log line now carries a
score_contributions field: a JSON array of the per-rule breakdown that produced the
verdict — [{ "module", "rule_id", "severity", "points" }, …]. Previously the log recorded
only the final rule_id + cumulative score; the full breakdown existed in memory but was
never emitted.

This is the data source for verdict drill-down (reconstructing why a request was
blocked, module by module). The field:

  • is emitted only on the already-logged denied path (never per benign request, never on the
    hot path);
  • is a JSON-string field, so a log consumer parses the line, then parses the field;
  • reconstructs losslessly into the typed model — waf_core::ScoreContribution and
    waf_core::Severity now derive Serialize/Deserialize (severity is lowercased:
    "critical" | "error" | "warning" | "notice"), so a downstream consumer can reuse the exact
    types.

The field names and format are a stable ingestion contract (locked by a shape test),
alongside the Prometheus metrics ABI.

Compatibility

  • Additive. New log field + new Serialize/Deserialize derives on two existing types.
    No API removed or changed; existing code compiles and behaves identically.
  • Detection unchanged — validation corpus 10/10, 0% false positives at PL3, same
    recall as v0.3.0 (this is a logging enrichment, not a detection change).
  • clippy -D warnings clean; full workspace test suite green, including a bite-test that drives
    a real blocked request through the proxy and asserts the enriched field is emitted and
    reconstructs into the typed breakdown.

Install / upgrade

# the binary (command: `waf`)
cargo install waf-proxy        # picks up 0.4.0

# or use the core as a library (open-core, no fork — embed via Proxy::builder + seams)
cargo add waf-core@0.4

Published crates

Crate Role
waf-core Base types, WafModule/Decision, StateStore seam
waf-normalizer Decode + NFKC + parsing + limits
waf-pipeline Phased orchestrator + anomaly scoring
waf-detection Detection modules + fast-path prefilter
waf-wasm Proxy-Wasm runtime (wasmi)
waf-proxy The binary (waf) + embeddable lib (ProxyBuilder)

License

Open source under Apache-2.0. The open-source vs. enterprise boundary is documented
normatively in BOUNDARY.md.

Light WAF v0.3.0

Choose a tag to compare

@0x00spor3 0x00spor3 released this 05 Jul 14:15

Light WAF v0.3.0 — Reload-surviving module injection (.module_factory)

A fast, modular Layer-7 Web Application Firewall written in Rust, operating as a
reverse proxy: it inspects every HTTP request, accumulates a CRS-style anomaly score,
and decides Allow / Block (403) / Reject (400 | 429) before forwarding to the backend.

This is a small, additive release focused on the embedding surface. There are no
changes to detection
— the corpus verdicts are identical to v0.2.0. The whole workspace is
bumped to 0.3.0 together (uniform versioning), even though only waf-proxy's public surface grew.

What's new

ProxyBuilder::module_factory — injected modules survive a reload (the headline)

Before 0.3, extra detection modules injected through the embedding builder
(.modules(..) / .add_module(..)) were built once and then silently dropped on a
config reload
(the core rebuilt the pipeline with an empty extra set). An embedder's
premium modules vanished on the first SIGHUP.

The new seam fixes that:

  • .module_factory(F) takes a Fn() -> Result<Vec<Box<dyn WafModule>>, _> + Send + Sync.
    The core runs it at bind and again on every config reload (in place of the old empty
    set), so injected modules survive a SIGHUP and are re-init'd — e.g. a changed
    GraphQL SDL or gRPC descriptor is re-read on reload.
  • Whole-set fallibility. The factory returns a Result as a unit: if a rebuild fails (a
    schema file went invalid on disk, a signed plugin no longer verifies), that reload is
    aborted and the last-good modules are kept — never an unprotected window, exactly like a
    rejected config. (Box<dyn WafModule> is not cloneable, so the whole-abort is precisely what
    preserves the working modules.)

WafModule itself is unchanged; this is a builder seam, not a trait change. The static
.modules(..) / .add_module(..) methods still exist (built once, boot-only) for tests and
simple embeds. See BOUNDARY.md
§4–§5 (additive builder evolution) and ARCHITECTURE.md §3/§9.

Compatibility

  • Additive builder surface. .module_factory and the public ModuleFactory type alias are
    new; existing Proxy::builder(..).build() and Proxy::bind(..) are unchanged.
  • One minor breaking edge: waf_proxy::config::LoadError gained a ModuleFactory(String)
    variant (a reload where the factory failed) and is now #[non_exhaustive]. Code that
    matched it exhaustively must add a _ => arm; most callers only Display it and are
    unaffected. This is the 0.x minor-version slot for such a change, and #[non_exhaustive]
    makes future additions non-breaking.
  • Detection unchanged — validation corpus 10/10, 0% false positives at PL3, same
    recall as v0.2.0 (the seam is a pure addition, not a detection change).
  • clippy -D warnings clean across the workspace; full test suite green, including two new
    integration tests proving an injected module survives a reload and that a factory error
    aborts the reload without dropping protection.

Install / upgrade

# the binary (command: `waf`)
cargo install waf-proxy        # picks up 0.3.0

# or use the core as a library (open-core, no fork — embed via Proxy::builder + seams)
cargo add waf-core@0.3

Published crates

Crate Role
waf-core Base types, WafModule/Decision, StateStore seam
waf-normalizer Decode + NFKC + parsing + limits
waf-pipeline Phased orchestrator + anomaly scoring
waf-detection Detection modules + fast-path prefilter
waf-wasm Proxy-Wasm runtime (wasmi)
waf-proxy The binary (waf) + embeddable lib (ProxyBuilder)

License

Open source under Apache-2.0. The open-source vs. enterprise boundary is documented
normatively in BOUNDARY.md.

Light WAF v0.2.0

Choose a tag to compare

@0x00spor3 0x00spor3 released this 30 Jun 08:46

Light WAF v0.2.0 — Extensible TLS seam (ACME / mTLS-ready)

A fast, modular Layer-7 Web Application Firewall written in Rust, operating as a
reverse proxy: it inspects every HTTP request, accumulates a CRS-style anomaly score,
and decides Allow / Block (403) / Reject (400 | 429) before forwarding to the backend.

This is a small, additive and backward-compatible release. There are no breaking
changes
and no changes to detection — the corpus verdicts are identical to v0.1.0.
The whole workspace is bumped to 0.2.0 together (uniform versioning), even though only
waf-proxy's public surface grew.

What's new

Extensible TlsCertSource seam (the headline)

The TlsCertSource trait gains two defaulted hooks — additive, so every existing
implementation (and FileCertSource) keeps working unchanged:

  • resolver() -> Option<Arc<dyn ResolvesServerCert>> — when Some, the core builds the
    rustls ServerConfig with a dynamic server-cert resolver instead of load(). The
    certificate can now rotate at runtime without rebuilding the acceptor (hitless rotation),
    and the listener advertises the acme-tls/1 ALPN so an on-listener TLS-ALPN-01 challenge
    can be negotiated on the same port.
  • client_verifier() -> Option<Arc<dyn ClientCertVerifier>> — when Some, the listener
    requires and verifies client certificates (mTLS / managed PKI). Default None → no client
    auth, exactly as before.

Both default to None, so the OPEN single-cert path (load()) is unchanged. This is the
injection point that lets an enterprise build managed-TLS — hitless ACME and mTLS
on top of the published crate without forking the core (compatible with the
BOUNDARY.md §5 ABI freeze,
the same pattern as WafModule::structural()).

Per-crate documentation on crates.io

Every published crate now ships its own README.md, so each crates.io
page documents that crate's role instead of showing only the one-line description.

Compatibility

  • No breaking changes. The two new trait methods are defaulted; existing code compiles and
    behaves identically.
  • Detection unchanged — validation corpus 10/10, 0% false positives at PL3, same
    recall as v0.1.0 (instrumentation/seams are pure additions, not detection changes).
  • clippy -D warnings clean, full workspace test suite green, TLS matrix re-gated.

Install / upgrade

# the binary (command: `waf`)
cargo install waf-proxy        # picks up 0.2.0

# or use the core as a library (open-core, no fork — embed via Proxy::builder + seams)
cargo add waf-core@0.2

Published crates

Crate Role
waf-core Base types, WafModule/Decision, StateStore seam
waf-normalizer Decode + NFKC + parsing + limits
waf-pipeline Phased orchestrator + anomaly scoring
waf-detection Detection modules + fast-path prefilter
waf-wasm Proxy-Wasm runtime (wasmi)
waf-proxy The binary (waf) + embeddable lib

License

Open source under Apache-2.0. The open-source vs. enterprise boundary is documented
normatively in BOUNDARY.md.

Light WAF v0.1.0

Choose a tag to compare

@0x00spor3 0x00spor3 released this 28 Jun 20:12

Light WAF v0.1.0 — First public release

A fast, modular Layer-7 Web Application Firewall written in Rust, operating as a
reverse proxy: it inspects every HTTP request, accumulates a CRS-style anomaly score,
and decides Allow / Block (403) / Reject (400 | 429) before forwarding to the backend.

This is the first release published to crates.io.

Highlights

Detection

  • Injection & evasion: SQLi, XSS, LFI/RFI, SSRF, RCE/Cmd-injection (incl. URL path,
    expression-language, VBScript/ASP webshell), MSSQL OS-exec procedures (xp_cmdshell…),
    LDAP, NoSQL, Mail (SMTP/IMAP), SSTI, SSI, XXE.
  • Scanner / tool fingerprinting on User-Agent (sqlmap, nuclei, OpenVAS, ffuf…) + OOB domains.
  • Path traversal, CRLF/header injection (field-aware), request smuggling (CL/TE) → 400.
  • GraphQL (structural): depth / alias / field / directive / batch caps → 400, introspection → 403.
  • gRPC (structural): protobuf de-framing into the content modules + message-size / field-count /
    nesting-depth caps and compressed-payload policy → 400 (HTTP/2, h2c with trailer relay).
  • Rate limiting L7 (token bucket per resolved client IP).

Anti-evasion normalization

Double-encoding-aware percent-decode, NFKC, pipeline-wide overlong-collapse, and a multi-transform
derived channel (decode-then-match-then-discard): base64, HTML-entity decode, mid-token
tag/control-strip, VBScript-concat de-obfuscation — also composed over base64 variants.

Transport & operability

  • TLS termination (rustls, cert-from-file): one port serves HTTP/1.1 and HTTP/2 via ALPN;
    fail-closed, no cleartext downgrade ([tls], opt-in).
  • External TOML config with semantic validation, per-scenario fail-open/closed ([resilience]),
    hot reload via SIGHUP (validate-then-swap, Unix), trusted-proxy client-IP resolution.
  • Configurable cumulative scoring, paranoia levels (PL1–4), structured JSON logging, and a
    fast-path that skips inspection on provably benign traffic (equivalence-tested).

Extensibility & observability (all OPEN, all default-off)

  • WASM plugins — a Proxy-Wasm runtime on wasmi loads
    .wasm filters as detection modules without forking the core (instance pool, fuel/memory DoS
    caps, fail-closed). See examples/wasm-plugin/.
  • OWASP CRS / ModSecurity import — a seclang parser + subset evaluator runs imported
    SecRule files as a module, with a boot skip-report for unsupported directives.
  • Prometheus metrics — opt-in /metrics export on a separate loopback listener.

Quality & validation

  • Detection is frozen and measured by a versioned corpus: 260 cases, 100% detection-recall
    on tracked-malicious cases and 0% false positives at PL3 (documented ExpectedMiss aside).
  • 9 custom parsers fuzzed (cargo-fuzz/ASan) + always-on cross-platform proptest invariants;
    ReDoS impossible by construction (linear-time regex, no backtracking).
  • Isolated inspection latency ~2 µs worst-case PL3 (p99 ~3.1 µs) — well under the < 1 ms contract.

Install

# the binary (command: `waf`)
cargo install waf-proxy

# or use the core as a library (open-core, no fork — embed via Proxy::builder + seams)
cargo add waf-core

Run with one of the example profiles:

waf --config examples/balanced.toml

Published crates

Crate Role
waf-core Base types, WafModule/Decision, StateStore seam
waf-normalizer Decode + NFKC + parsing + limits
waf-pipeline Phased orchestrator + anomaly scoring
waf-detection Detection modules + fast-path prefilter
waf-wasm Proxy-Wasm runtime (wasmi)
waf-proxy The binary (waf) + embeddable lib

License

Open source under Apache-2.0. The open-source vs. enterprise boundary is documented
normatively in BOUNDARY.md.