Workstation Logo
Products
AI LabsOpenAI AgentsCRMMarketingAll Products
AI Solutions
AI WorkstationsAI SME PackagesPrivate AIGPU ClustersEdge AIEnterprise AI LabAI by Industry
Services
Platform ModernisationDigital EngineeringData Foundations & AIAutonomous OperationsAI ConsultancyDevOps AutomationCyber SecuritySoftware DevelopmentAgent BuildingMLOps Setup
About Us
PartnersCustomer Stories
Articles
Documentation
WSL ProxyRing Promoter
Blog
Contact UsLogin
Workstation

AI workstations, AI Multi Agentic Software, GPU infrastructure, and intelligent agent solutions for modern businesses.

UK Office: 77-79 Marlowes, Hemel Hempstead HP1 1LF - Directions - Take Junction 20 off M25 Outer London
Company No: 11641870
Mon - Fri: 9:00 AM - 6:00 PM GMT
+44 7515 356 146

Belgium Office: Workstation SRL, Rue Vanderkindere 34, 1180 Uccle, Brussels
BE 0751.518.683
Mon - Fri: 9:00 AM - 6:00 PM CET
+32 492 45 67 46

India Office: #159 Sector 9, Pocket 1, DDA Flats, 110077 Dwarka, New Delhi
+91 98881 98841

Products

All ProductsWSL ProxyRing PromoterAI LabsOpenAI Agents

AI Solutions

AI SolutionsAI WorkstationsPrivate AIGPU ClustersEnterprise AI LabServices

Resources

ArticlesDocumentationBlogSearch

Company

About UsPartnersContact

ยฉ 2026 Workstation AI. All rights reserved.

PrivacyCookies
Home / Articles / Technology
SecurityDevOpsSREAPI Gateway

Inside Workstation WSL Proxy WAF Engine v2: Stages, Signatures, and Binding Precedence

Stages, signatures, binding precedence, and the roadmap beyond MVP

September 17, 2026Technology18 min read

Workstation WSL Proxy (WSLProxy) runs WAF Engine v2 on the OpenResty request path: eight first-class stages, governed signatures, binding precedence, and explainable 403s with support IDs. This article is a faithful technical walkthrough of the engine design reference for a Workstation audience. Companion blog: WSLProxy WAF Engine v2 ยท Product WAF: wslproxy.org/waf ยท Overview: /wsl-proxy. A dedicated WSLProxy docs site comes later; until then the canonical engine markdown remains in the upstream repo.

Workstation WSL Proxy WAF Engine v2 cover

Watch: WSL Proxy WAF tour

Workstation WSL Proxy WAF tour

Watch: youtu.be/r10XSonA5JE ยท Product: wslproxy.org ยท WAF: wslproxy.org/waf ยท Engine doc: WAF_ENGINE_V2.md

Agent digest.
  • Status: Implemented MVP in api/waf_engine.lua, api/waf_stages.lua, api/waf_support.lua.
  • Path: rate-limit โ†’ WAF inspect (fail-open) โ†’ stages โ†’ governed signatures โ†’ anomaly.
  • Modes: blocking | transparent; route override > server override > policy default.
  • Explain: policy, binding, stage, violation code, signature ID, X-Support-ID.
  • Roadmap: MVP = done today; Phase 2 / Phase 3 = future. Fail-closed body parse is P2.

1. Goals and design constraints

Engine v2 aims to be a production WAF that platform and security teams can bind per domain, per service label, and per route; run in blocking or transparent mode; govern by stable signature IDs; and operate with structured logs plus correlation IDs โ€” not a regex snippet. Positioning is inspired by F5 WAF for NGINX (App Protect) goals. This article does not claim F5 feature parity.

Constraint Meaning
In the NGINX pathLuaJIT, per-worker compiled-regex cache (o flag on ngx.re.find), shared dicts for velocity counters. No blocking I/O on the hot path.
Fail-open by defaultAny engine or stage error logs and allows the request. A WAF bug must never take the site down. Parse-error fail-closed on untrusted bodies is a policy option on the roadmap (Phase 2), not the default.
Backward compatibleA v1 policy (just waf_rules + mode) keeps working unchanged; every v2 field is optional and additive.
ExplainableEvery block names the policy, the binding that won, the stage, the violation code, the signature ID, and a support ID.

Machine-readable policy shape lives in upstream docs/waf-policy.schema.json. The narrative source for this article is docs/WAF_ENGINE_V2.md.

2. Request-path architecture

Like a solutions playbook: match the route once, inspect under a fail-open wrapper, then proxy or protect.

rewrite_by_lua  gateway_ack.lua      select route rule
                     โ”‚
                     โ–ผ
                gateway_pipeline.execute()
                     โ”‚  Phase 2 rate-limit โ†’ Phase 3 WAF โ†’ โ€ฆ
                     โ–ผ
                waf_engine.inspect(server_config, profile_id)   โ”€โ”€ fail-open wrapper
                     โ”‚
                     โ–ผ
                _inspect_impl:
                  load policy (30s TTL cache)
                  resolve effective mode + winning binding
                  โ”Œโ”€โ”€ STAGE PIPELINE (waf_stages.PIPELINE) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                  โ”‚  1 method allow-list      VIOL_METHOD                โ”‚
                  โ”‚  2 filetype deny          VIOL_FILETYPE              โ”‚
                  โ”‚  3 smuggling / desync     VIOL_SMUGGLING             โ”‚
                  โ”‚  4 ip lists โ†’ geo         VIOL_IP_DENY / VIOL_GEO    โ”‚
                  โ”‚  5 jwt alg policy         VIOL_JWT_ALG               โ”‚
                  โ”‚  6 json body profile      VIOL_JSON_SIZE/DEPTH       โ”‚
                  โ”‚  7 brute-force velocity   VIOL_BRUTE_FORCE           โ”‚
                  โ”‚  8 openapi positive-sec   VIOL_OPENAPI_PATH/METHOD   โ”‚
                  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”Œโ”€โ”€ SIGNATURE MATCHING (governed) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                  โ”‚  per rule: disabled? set-disabled? staged?           โ”‚
                  โ”‚  match target โ†’ block | alarm                        โ”‚
                  โ”‚  anomaly score โ‰ฅ threshold โ†’ VIOL_ANOMALY_SCORE      โ”‚
                  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                     โ”‚
                     โ–ผ  finding
                block_request(): 403 + X-WAF-Block + X-WAF-Rule +
                                 X-WAF-Violation + X-Support-ID + block page

Each stage is a pure function (policy, ctx) โ†’ finding|nil. The engine โ€” not the stage โ€” decides block vs alarm from the effective enforcement mode, so the same rule set behaves differently per binding without duplication.

  1. Request arrives at a POP โ€” OpenResty accepts TLS and enters the Lua hot path.
  2. Match a routing rule โ€” gateway_ack.lua selects the live route.
  3. Pipeline โ€” rate-limit, then waf_engine.inspect, then response action / balancer.
  4. Inside inspect โ€” load policy, resolve binding, stages, then signatures and anomaly.
  5. On block โ€” 403 with WAF headers, support ID, and optional branded block page.

Two implementation details matter for capacity planning. Policy load uses a short TTL cache (about thirty seconds) so hot traffic does not re-read disk or remote config on every request. Signature matching leans on LuaJIT and a per-worker compiled-regex cache; velocity counters for brute-force live in shared dicts. That is the concrete meaning of โ€œin the NGINX pathโ€: the WAF must finish without blocking I/O that would stall the worker.

Fail-open wrapping sits around the inspect implementation. If a stage throws or the engine hits an unexpected error path, the request is allowed and the failure is logged. That availability bias is a deliberate product choice for MVP. Teams that want fail-closed behaviour on untrusted body parse errors should track Phase 2 โ€” it is not the default today.

2b. Stage-by-stage behaviour (MVP)

Before signatures run, eight first-class stages walk the request. Order is fixed in waf_stages.PIPELINE. Understanding each stage is how you design policy packs without treating the WAF as a black box.

Method allow-list (VIOL_METHOD). Only permitted HTTP methods proceed. A payments API that only needs GET, POST, HEAD, and OPTIONS can deny TRACE, PUT, and other unexpected verbs before any regex work runs. This is cheap positive security for verb surface.

Filetype deny (VIOL_FILETYPE). Sensitive extensions such as .env, .sql, .bak, .git, and .pem are blocked when present on the path. This catches common reconnaissance and misconfiguration probes that never look like SQLi.

HTTP smuggling / desync (VIOL_SMUGGLING). The smuggling stage enforces CL/TE desync guards: Content-Length plus Transfer-Encoding coexistence, obfuscated or duplicate Transfer-Encoding, and malformed Content-Length. Chunked transfer can be allowed when allowChunked is true. A companion signature (waf-rule-smuggling-001) covers body-embedded request lines. Together they address a class of attacks that signature-only WAFs often miss because the abuse is in framing, not payload keywords.

IP lists and geo (VIOL_IP_DENY / VIOL_GEO). Allow and deny CIDRs plus deny-country lists via an IP2Location-style database path on the policy. This is complementary to routing-rule geo matches elsewhere on the POP: WAF stages record violation codes and support IDs; routing rules may redirect or choose a different upstream without a WAF finding.

JWT algorithm policy (VIOL_JWT_ALG). Deny weak algorithms (including none and HS256 where your threat model requires it) and optionally require stronger algorithms such as RS256 or ES256. MVP checks algorithm policy from the Authorization header configuration โ€” full JWKS signature verification is Phase 2 (future).

JSON body profile (VIOL_JSON_SIZE / VIOL_JSON_DEPTH). Cap JSON depth and body size before the request reaches the origin. Oversized or deeply nested JSON is a common parser-abuse vector; stopping it early also saves signature regex cycles.

Brute-force velocity (VIOL_BRUTE_FORCE). Per-path sliding windows (for example /api/login with a sixty-second window and a max attempt count) keyed by IP via shared dicts. Action can block when thresholds trip. This is not behavioural L7 DoS (that is Phase 3); it is explicit velocity control on declared paths.

OpenAPI positive security (VIOL_OPENAPI_PATH / VIOL_OPENAPI_METHOD). Allow only declared path+method surfaces, including path templating. Unknown surface never reaches the backend when the policy is wired that way. Parameter and type validation from a full OpenAPI spec is Phase 2; MVP is the path+method allow-list surface.

Positive security and negative signatures are meant to work together. OpenAPI path+method shrinks the attack surface; signatures catch known patterns on what remains. JSON profiles stop parser abuse before expensive matching. Smuggling guards protect the framing layer that regex on body text cannot see cleanly.

3. Policy schema v2 (annotated)

High-level fields: enforcementMode (blocking|transparent), service label, anomaly_threshold, waf_rules[], signatureSets, signatures.disable / signatures.stage, methods, filetypes, smuggling, geo, ipLists, jwt, jsonProfile, bruteForce, routeOverrides, logging, blocked_response, whitelist. Schema file: upstream docs/waf-policy.schema.json.

Payments-hardened example adapted from the engine doc:

{
  "id": "waf-policy-payments-hard",
  "name": "Payments API โ€” Hardened",
  "schema_version": 2,
  "enabled": true,
  "enforcementMode": "blocking",
  "service": "payments",
  "anomaly_threshold": 6,

  "waf_rules": ["waf-rule-sqli-001", "..."],
  "signatureSets": [
    { "id": "SET_SQLI", "block": true, "alarm": true }
  ],
  "signatures": {
    "disable": ["waf-rule-xss-005"],
    "stage": [
      { "id": "waf-rule-openredirect-001", "until": "2026-12-31T00:00:00Z" }
    ]
  },

  "methods":   { "allow": ["GET","POST","HEAD","OPTIONS"] },
  "filetypes": { "deny": [".env",".sql",".bak",".git",".pem"] },
  "smuggling": { "enforce": true, "allowChunked": true },
  "geo":       { "denyCountries": ["KP"], "db": "/tmp/IP2LOCATION-LITE-DB11.IPV6.BIN" },
  "ipLists":   { "allow": ["10.0.0.0/8"], "deny": ["5.6.7.0/24"] },
  "jwt":       {
    "header": "Authorization",
    "denyAlg": ["none","HS256"],
    "requireAlg": ["RS256","ES256"]
  },
  "jsonProfile": { "maxDepth": 8, "maxBytes": 16384 },
  "bruteForce":  [{
    "path": "/api/login",
    "windowSec": 60,
    "maxAttempts": 5,
    "action": "block",
    "keyBy": ["ip"]
  }],

  "routeOverrides": [
    { "path": "/preview", "enforcementMode": "transparent" }
  ],

  "logging": { "profile": "verbose", "destination": "syslog" },
  "blocked_response": {
    "status_code": 403,
    "content_type": "text/html",
    "body_base64": "โ€ฆ{{support_id}}โ€ฆ"
  },
  "whitelist": { "ips": ["127.0.0.1"], "paths": ["/health"], "user_agents": [] }
}

enforcementMode is the v2 name; transparent aliases monitor-style behaviour. service appears in logs and binding context as a logical app label. routeOverrides are how you keep marketing preview paths in alarm while payment routes block.

Read the example as an operable pack, not a template to paste blindly. anomaly_threshold: 6 only makes sense after you have watched score distributions in transparent mode. Disabling waf-rule-xss-005 is the escape hatch for a known false positive; staging open-redirect until a hard timestamp is the controlled promotion path. Whitelist paths such as /health keep probes and load balancers out of the security noise. The blocked_response body can embed {{support_id}} so the human who hit the block page carries the same ID your on-call will search for.

Backward compatibility matters during migration. A v1 policy that only sets waf_rules and mode continues to work. You can add schema_version: 2 fields incrementally โ€” route overrides first, then smuggling, then OpenAPI โ€” without rewriting every virtual host on day one.

4. Signature schema โ€” stable IDs as the addressable unit

{
  "id": "waf-rule-ssti-001",
  "name": "SSTI โ€” Template Expression Injection",
  "category": "ssti",
  "signature_set": "SET_SSTI",
  "severity": "high",
  "target": "all",
  "pattern": "(?:\\{\\{[^}]{0,120}?\\}\\}|โ€ฆ)",
  "pattern_type": "regex",
  "action": "block",
  "score": 8,
  "tags": ["api-security","ssti"],
  "references": ["CWE-1336","OWASP-A03"]
}

The stable id is what you disable, stage, or cite in tickets. Category maps to a default set id SET_<CATEGORY> unless signature_set is set explicitly. Target is one of url, args, body, headers, cookies, user_agent, or all. Pattern type is regex or string. Action is block or monitor (alarm-only). Score contributes to the anomaly total when enforcing. references (CWE/OWASP) are present on the schema; rendering them in events is Phase 2 (future).

Treating the signature ID as the addressable unit is what makes governance workable for humans and agents. You do not ask on-call to โ€œturn off the SSTI regex somewhere in the pack.โ€ You ask them to stage waf-rule-ssti-001 until a timestamp, or to set SET_SSTI to block:false while you investigate. CI validation (tools/waf_validate.py) checks referential integrity so policy packs cannot point at missing signature IDs.

Do not invent Lua APIs or custom request headers beyond what the engine doc specifies. Operators should rely on the published response headers and wafsec fields. Pattern text in examples is illustrative; production signatures live in the upstream rule set and Admin UI, not in this articleโ€™s HTML.

5. Binding resolution

Precedence, most specific wins:

route override  >  per-server (waf_mode_override)  >  policy default (enforcementMode)
mode    = normalize(policy.enforcementMode or policy.mode)   -- "block" | "monitor"
binding = "domain"
if server.waf_mode_override then
  mode = server.waf_mode_override
  binding = "server"
end
best = -1
for ro in policy.routeOverrides:
  if methodMatches(ro, ctx.method)
     and ctx.path startswith ro.path
     and len(ro.path) > best then
    best    = len(ro.path)
    mode    = normalize(ro.enforcementMode or ro.mode)
    binding = "route:" .. ro.path
  end
return mode, binding

binding is recorded on every finding, so a log line answers which binding won. Longest-prefix wins makes /api/admin beat /api. A service is a logical label (server.waf_service or policy.service) that rides along in logs; service-level policy selection (a service โ†’ policy map) is Phase 2 (future).

Mode Behaviour
BlockingViolations return 403 with support ID and optional branded block page.
Transparent / monitorViolations alarm and log only โ€” dry-run and staging.

Binding is the property the engine brief calls out as critical for explainability. Without it, two hosts sharing a policy pack look identical in logs even when one server override flipped a path to transparent. With it, a single wafsec line tells you whether domain default, server override, or a specific route prefix won. That is what turns a 403 into an operable incident.

Practical pattern: keep the policy default in blocking for a hardened API host; add a transparent route override for /preview or a canary path; use a server-level override only when an entire virtual host must dry-run. Prefer route overrides for surgical changes โ€” they are the most specific and the easiest to reason about in tickets.

6. Signature governance and anomaly score

Three independent controls, evaluated per rule during matching:

Control Source Effect
disablesignatures.disable: [id]Rule skipped entirely.
stagesignatures.stage: [{id, until}]Rule alarms only until the timestamp, then enforces โ€” the safe-rollout path.
set togglesignatureSets: [{id, block}]block:false downgrades a whole set to alarm-only.

A staged or set-disabled signature alarms and does not contribute to the anomaly score, so staging can never cause a block indirectly. A ruleโ€™s set is signature_set or SET_<CATEGORY>. When cumulative score of enforcing matches meets or exceeds anomaly_threshold, the engine raises VIOL_ANOMALY_SCORE.

That anomaly rule is easy to get wrong if staging still scored. Engine v2 forbids the foot-gun: staged and set-disabled matches alarm for visibility but leave the cumulative score alone. You can therefore soak a noisy set in production transparent bindings, or stage individual IDs under a blocking policy, without discovering later that โ€œmonitor noiseโ€ quietly crossed the threshold.

Recommended promotion sequence for a new signature or set:

  1. Add or enable the signature with stage until a known timestamp, or with set block:false.
  2. Watch waf_monitored and wafsec lines for false positives on real traffic.
  3. Disable individual IDs that are clearly wrong; keep the rest.
  4. Remove the stage window or flip the set to block:true on the highest-value routes first.
  5. Only then lower or enable anomaly_threshold if your score model needs it.

7. Observability

  • Correlation ID โ€” every block/alarm gets WSL-<epoch>-<rand>, echoed as X-Support-ID and rendered into the block page ({{support_id}}).
  • Structured security log โ€” one JSON line per decision, tagged wafsec for syslog/OTel shipping: support_id, action, code, stage, signature_id, signature_set, category, severity, policy, service, binding, host, client_ip, method, uri, latency_us. Also retained in the waf_events shared dict for the recent-events API.
  • Metrics โ€” Prometheus counters waf_blocked, waf_monitored, waf_inspections, waf_latency, waf_errors by host/category/severity.
  • Response headers โ€” X-WAF-Block, X-WAF-Rule, X-WAF-Violation, X-Support-ID.

When a customer or internal user pastes a support ID into a ticket, look that ID up in events/logs. Do not guess which anonymous rule fired on another appliance.

On-call checklist for a WAF block:

  1. Collect X-Support-ID (and X-WAF-Violation / X-WAF-Rule if present).
  2. Query recent events or wafsec for that support ID.
  3. Confirm policy, binding, stage, signature_id, and service.
  4. Decide: false positive โ†’ disable or stage the ID; true positive โ†’ keep blocking and fix the client or upstream.
  5. If the block was unexpected on a path that should be transparent, inspect route overrides and server waf_mode_override before changing signatures.

Prometheus series give you the fleet view: rising waf_errors means investigate fail-open paths and stage health; rising waf_blocked without a deploy may mean an attack or a bad promotion; waf_latency tells you whether inspection cost is acceptable on the hot path.

8. Violation code catalogue

Code Stage
VIOL_METHODMethod allow-list
VIOL_FILETYPEFiletype deny
VIOL_SMUGGLINGHTTP request-smuggling / desync guard (CL+TE, obfuscated/duplicate Transfer-Encoding, malformed Content-Length)
VIOL_IP_DENY / VIOL_GEOIP lists / geo
VIOL_JWT_ALGJWT algorithm policy
VIOL_JSON_SIZE / VIOL_JSON_DEPTHJSON body profile
VIOL_BRUTE_FORCEVelocity control
VIOL_OPENAPI_PATH / VIOL_OPENAPI_METHODOpenAPI positive security
VIOL_ATTACK_SIGNATURESignature match
VIOL_ANOMALY_SCORECumulative score โ‰ฅ threshold

9. MVP checklist (shipped) vs Phase 2 / Phase 3 (future)

MVP โ€” done today

  • Policy bind per domain + per route
  • Signature sets / per-ID enable-disable-stage
  • Method & filetype allow/deny
  • IP allow-deny + geo country deny
  • JWT algorithm policy
  • JSON body depth/size profile
  • Brute-force velocity
  • OpenAPI positive security (declared path+method allow-list, path templating)
  • HTTP request-smuggling / desync guard (smuggling stage โ†’ VIOL_SMUGGLING, plus waf-rule-smuggling-001 for body-embedded request lines)
  • Structured security log + support IDs
  • Prometheus metrics
  • Block page with support ID
  • Golden tests (examples/wslproxy-waf-demo/waf_features.py)
  • CI validation (tools/waf_validate.py + waf-validate workflow: Lua syntax + JSON Schema policy validation + signature referential integrity)
  • Admin UI (react-admin WafPolicies/WafRules forms cover every v2 field)

Phase 2 โ€” future

  • Service โ†’ policy binding map (logical app selection), not just a label
  • OpenAPI parameter/type validation from a full spec (today: path+method surface)
  • XML profile (DTD/entity off, depth/size) as a first-class stage (today XXE is caught by signatures)
  • GraphQL depth/batch/introspection profile (today introspection is a signature)
  • Cookie integrity / attribute enforcement; JWT JWKS signature verify
  • Response Data Guard (PAN/SSN masking in the body_filter phase)
  • references (CWE/OWASP) rendered in logs and the events API
  • Configurable fail-closed on body parse errors in blocking mode
  • Bot classes beyond UA (header/JA3 signals); threat-campaign pack channel
  • aegisctl-style CLI: compile | validate | test | bench; policy unit tests

Phase 3 โ€” future

  • Behavioural L7 DoS (token bucket + per-object anomaly)
  • IP-reputation feed adapter; MaxMind pluggable geo
  • gRPC/protobuf malformed detection
  • Ingress CRDs (WAFPolicy, WAFBinding) + GitOps policy validation in CI
  • Hyperscan/Vectorscan matching backend when present (FFI), Aho-Corasick fallback

Do not blur Phase 2 or Phase 3 with the MVP. If a feature is not in the MVP list above, treat it as future unless your deployed build documents otherwise.

Phase 2 is mostly about deeper profiles and stronger identity on the same architecture: serviceโ†’policy maps, richer OpenAPI, XML/GraphQL profiles, JWKS, Data Guard, fail-closed parse options, and better packaging for threat packs and CLI workflows. Phase 3 is mostly about heavier detection backends and Kubernetes-native policy objects. Neither phase is a licence to claim those capabilities on the Workstation site today.

Golden tests in examples/wslproxy-waf-demo/waf_features.py exist specifically to prove per-binding actions for the MVP surface. Prefer those fixtures over inventing catch-rate metrics. This article does not publish efficacy percentages, customer names, or ship dates for Phase 2 or Phase 3.

10. Non-goals (v1)

  • Full RASP / in-process application instrumentation
  • Copying proprietary signature databases
  • In-path ML training

Heuristics and signatures first. That keeps the hot path honest and the explainability model tractable. Full RASP would move instrumentation into application runtimes โ€” a different product class. Cloning proprietary signature databases creates legal and operational risk without improving explainability. In-path ML training would violate the โ€œno blocking I/O / keep the worker fastโ€ constraint that shaped Engine v2.

If your requirement is โ€œtrain a model on live traffic inside the reverse proxy,โ€ Engine v2 is the wrong tool. If your requirement is โ€œbind an explainable policy pack per route on OpenResty with support IDs,โ€ it is the right one.

11. How to operate today

Brief pointers only โ€” full runbooks belong with the product and future docs site:

  1. Admin UI โ€” WafPolicies / WafRules forms cover v2 fields: bind to servers, override mode, review events.
  2. CI validate โ€” run upstream tools/waf_validate.py (Lua syntax, JSON Schema policies, signature referential integrity) rather than hand-editing production JSON on a live POP.
  3. Golden tests โ€” examples/wslproxy-waf-demo/waf_features.py exercises per-binding actions where you run the demo pack.
  4. Rollout practice โ€” bind in transparent/monitor first; stage noisy signatures; promote route by route; enable anomaly threshold only after you understand score distributions in monitor.
  5. Control plane siblings โ€” Swagger REST, MCP tools, and wslproxy-cli for waf_rules / waf_policies remain part of the operable edge story described on the product page.

Fail-open remains the default for engine and stage errors. That is intentional availability bias.

Layered controls outside the WAF engine still matter on the same POP: per-server rate limiting before expensive origin work, CAPTCHA challenges on routing rules, and JWT/S3/cookie matches that deny before the backend sees the request. Design WAF packs with those siblings in mind. A signature-only mindset recreates the โ€œregex snippetโ€ failure mode Engine v2 was built to replace. For the broader edge narrative โ€” gateway, CDN, and WAF together โ€” see the earlier Workstation WAF article and the product page; this piece stays focused on the engine design reference.

12. Related reading

  • Companion blog: WSLProxy WAF Engine v2: Enterprise Enforcement You Can Explain
  • Broader WAF narrative: WSL Proxy WAF ยท Explainable edge WAF article
  • Product page: Workstation WSL Proxy
  • Public WAF feature page: wslproxy.org/waf
  • Engine design reference (GitHub): docs/WAF_ENGINE_V2.md
  • Source: github.com/bwalia/wslproxy

13. Closing

Edge WAF earns trust when operators can explain every block, dry-run every change, and promote policy without waiting for a full POP reload. Workstation WSL Proxy WAF Engine v2 puts that workflow on the OpenResty hot path: stages first, governed signatures second, binding precedence you can log, and support IDs on the wire.

Docs website deferred โ€” this article points at GitHub docs/WAF_ENGINE_V2.md and wslproxy.org/waf. Watch the tour, start in transparent mode, and promote to block when your events stream says the policy is ready. Until the dedicated docs site exists, treat this Workstation article plus the upstream markdown as the public deep dive for Engine v2.

Published by Workstation.

Continue on the product site: https://wslproxy.org/waf/

Share this article

More in Technology

Workstation WSL Proxy โ€” Docker Image Optimisation, Build Cache, Full Deploy Workflow, and Shipping It with AI Assistance

Workstation WSL Proxy โ€” Docker Image Optimisation, Build Cache, Full Deploy Workflow, and Shipping It with AI Assistance

Technical brief: prebuilt OpenResty Dockerfile, Buildx/GHA cache, Ansible extract, delivery pipeline DEPLOY_MODE, and an operator+agent loop for finishing pipeline work

Read more
Claude Code, Claude Cowork & ChatGPT for Business Teams

Claude Code, Claude Cowork & ChatGPT for Business Teams

Claude Code vs Claude Cowork vs ChatGPT/OpenAI Agents: team matrix, GPT-5.6/GPT-6 class APIs, MCP OAuth, and approval gates

Read more
Enterprise Agentic Frameworks: LangChain, LangGraph & Airflow 3

Enterprise Agentic Frameworks: LangChain, LangGraph & Airflow 3

LangChain/LangGraph/LangSmith, Apache Airflow 3.x, MCP gates, Ring Promoter, and OTel cost control for enterprise agent workflows

Read more