01Goal and principle
What we build and why. ENISA needs to protect its CRA portal and its data-submission site. We deliver a top-tier solution built on security-by-design.
The TLS termination boundary = the data-processing boundary. In the sensitive part WGP does not terminate → does not process content → it decides only by the profile (L3/L4) learned where we do terminate (login). It lets the good through, blocks the bad, and sends the unknown to login. Authorization is held by the application; WGP is an attack filter.
02Traffic planes and three zones
We split traffic so that as little as possible — and as controlled as possible — reaches the sensitive part.
| Plane | Content | TLS | How we protect |
|---|---|---|---|
| CDN (static) | Images, CSS, scripts — separate domain, separate certificate, no sensitive data | terminated at CDN | Fully under our control; the separate domain prevents HTTP/2 coalescing with the sensitive host |
| Zone 1 — portal | Landing page (static) | terminated | Fully cached + microcaching → static is practically unattackable |
| Zone 2 — login | Login | terminated (box) | Background bot detection + tiered challenge; the profile is learned at login |
| Zone 3 — sensitive | Accounts, data submission | passthrough | No cert. Only verified (OK) passes from AL4 straight to origin; KO blocked; otherwise not allowed |
03Control layers — anycast POP and dedicated box
Every point has several layers (different HAProxy instances and other tools). On the box, one sticky table shares state across the whole box; Redis distributes it to other boxes and up to the edge.
04What happens to a request
Depending on the content type and the visitor state (unknown / OK / KO) the request goes elsewhere. Switch the scenario and follow the path and what happens.
05Login and bot detection
We send the request to the login portal together with a bot check — in the background, invisibly. The selection is selective by IP type.
| Visitor type | Approach |
|---|---|
| Home / residential IP | Invisible bot detection first; a challenge only on suspicion |
| Datacenter / VPN / Tor | Straight to a harder challenge (PoW / visible captcha) |
| Known bad IP / fingerprint | KO at the edge — never even reaches login |
Successful authorization → the client is marked OK (profile). A few failures → KO. Both are written locally, to the box-shared sticky and to Redis.
06Multi-layer limits and rules
On every layer (anycast and dedicated) we can limit or block connection and access counts (including at L7) and other metrics, and apply rules accordingly.
| Dimension (and combinations) | Metric | Action |
|---|---|---|
| per IP · per ASN · per IP category | connection count, concurrency, request count (L7), rate | PASS — allowed PoW — challenge of varying difficulty BLOCK — various methods |
| per country (GeoCC) · per continent | ||
| combined with JA4 / JA4T / other parameters |
A block can return an info page (what happened, who to contact, a request summary) served from CDN cache — attacking this page does not matter, it is fully cached and contains no sensitive data. Example in section 07.
07Block page (from CDN cache)
Example of the page a blocked visitor sees. Themed like our "origin down" pages: explanation, contact, a copyable request summary. Available in all official EU languages. WEDOS and ENISA logos.
Access temporarily restricted
Request blocked by WEDOS Global Protection
This request was temporarily blocked by attack protection. If you are an authorized user, please try again shortly, or sign in. If the problem persists, contact support and include the request summary below.
Ref: a1b2c3d4-ee55-46f7-9a01-d3f2c7b18e90 Time: 2026-06-28 09:41:07 UTC Reason: rate-limit / reputation Node: edge-anycast (LU)
Contact: soc@enisa.europa.eu · wedos.com
Available in all 24 official EU languages. The page is fully cached in the CDN — attacking it requires no origin resources.
08Profile and signals
The profile has two levels. The rich profile decides on the box (where we terminate). The thin key propagates downward — only from it can AL4/edge evaluate OK/KO.
| Signal | Level | At AL4? | Note |
|---|---|---|---|
| IP | thin key | ano | unstable (mobile) — strict: change = block + re-login |
| JA4 | thin key | from learning | native after termination (ACT/ACU) |
| JA4T | thin key | from XDP | from SYN; XDP at edge → TLV (phase 2) |
| ASN / GeoCC / IP class | thin key | ano | stable complement; drives challenge selection |
| JA4H · HTTP/2 · QUIC fp | rich | ne | only after termination; refines the OK decision |
For the protected portal (zones 2/3) enforce HTTP/2 only (TCP) — disable HTTP/1.1 and HTTP/3. One ALPN, one transport, no fingerprint change during the session. Also JA4T requires TCP — it does not exist over HTTP/3 (QUIC). The machine-to-machine endpoint (data submission) is handled separately (mTLS); CDN/static may keep h3.
Combining signals increases uniqueness, not unforgeability — a targeted attacker clones the fingerprint. So it is a filter, not a gate; the real boundary remains auth at the origin.
09Propagation, latency and failure
State is written only by the terminating layer (ALB, after the auth result). AL4 and edge only read. The hot path always reads locally; the times below are for replication only — Redis is never in the synchronous path.
| Propagation layer | Latency | Note |
|---|---|---|
| Local sticky (same HAProxy) | instant | process memory |
| Box-shared sticky (local peers) | sub-ms – a few ms | between processes on one box |
| Redis → other boxes (in region) | a few – tens of ms | replication, not hot path |
| Redis → edge POPs / cross-continent | tens – ~100+ ms | WAN RTT; KO propagates globally |
We balance traffic across all locations, so a failure = only reduced capacity. The edge removes the failed location from balancing; a new box reads the profile from Redis → the client continues without re-login (if within the window). If the record has expired (300 s) or was not yet replicated → re-login. KO propagates the same way → the attacker stays blocked.
10Topology and failover — we recommend 3+ locations
We balance traffic across all dedicated locations (active/active). A failure of one = reduced capacity, not a service outage. With 3+ we can service one while another may simultaneously fail, and still keep running.
| Number of locations | Assessment |
|---|---|
| 1 | SPOF — failure = service outage. Distant users high RTT. Maintenance = outage. Not for production. |
| 2 | HA yes, but on failure the other carries 100% load (2× headroom required). Even number → no quorum. Limited coverage. |
| 3+ recommended | Survives one failure with distributed headroom (n−1). Better coverage and RTT. Odd number enables quorum. Rolling maintenance without outage. |
11Termination vs passthrough
The point where the lock opens is the TLS termination point = where WGP sees into the content. For the sensitive zone it opens only at the origin.
We block KO first on the dedicated box. Once KO propagates via Redis to the edge, we block it already at the anycast POP — an extra situation: cheapest and global, the attacker never reaches the box.
12Alternative: termination + E2EE payload (HPKE)
Proposed as an alternative / option to passthrough zone 3 (not a replacement). When ENISA wants full L7 protection on the sensitive flow together with a guarantee that we cannot read the content: we terminate TLS, but the request/response bodies are end-to-end encrypted with a key we never hold. In exchange for integrating client-side crypto into ENISA's application.
The client (JavaScript in the browser or a machine-to-machine client) wraps only the body in an HPKE envelope (RFC 9180) to the ENISA server's public key. Only the payload is encrypted, not the whole HTTP — structure, headers, sizes and JA4 stay readable to us for L7 protection. We terminate the outer TLS, see only ciphertext, and over the envelope we apply rate-limiting, bot detection, challenges and structural WAF (valid envelope format, max size) before the origin. The origin decrypts the envelope; its private key is in an HSM. Note: do not use nested TLS (TLS-in-TLS) — that would turn the body into opaque bytes and we would lose L7 protection. Only the payload is encrypted.
HPKE for humans and machines — one envelope, separate key acquisition
Yes, HPKE can be used for humans too. The data plane is one envelope for everyone; only how the client obtains the key and proves it is authorized differs. Confidentiality to the server is available to everyone — HPKE Base mode to the server's public key works even without a client key. Sender authentication (“authorized only”) is provided by Auth mode for anyone holding a client key; for password users it is replaced by an OPAQUE session.
| Participant | Enrollment | Key storage | Proof of authorization | Rotation / revocation | Move to another device |
|---|---|---|---|---|---|
| Machine-to-machine (M2M) | keypair issued at provisioning | secret manager / HSM — key never leaves the client | registered public key on the server (Auth mode) | new key + remove old from registry | n/a (client is a machine) |
| Human · trusted device | browser generates a keypair at first login | IndexedDB — non-extractable key (WebCrypto) | registered public key (Auth mode) | re-enroll the device key | via OPAQUE fallback or key escrow |
| Human · password only | no key in advance | no storage — key is unlocked from the password | OPAQUE (PAKE) session + optionally a wrapped key | password change = re-wrap the key | works on any device (password is enough) |
Bidirectional: from one HPKE setup we export a session key and both request and response run over AES-GCM with one key. Recovery must be E2EE-safe (recovery code / escrow share), not silent server reading. The server holds: its own HPKE keypair (HSM), a registry of authorized client public keys (Auth mode + revocation) and OPAQUE verifiers for password users. Browser-native path: ECDH(P-256) + HKDF + AES-256-GCM (all in WebCrypto), or an HPKE library (often WASM). OPAQUE/Argon2 require a library.
Weakness: integrity of crypto-code delivery
In the browser, E2EE is only as strong as the integrity of the code that encrypts. Anyone who serves the page or can modify it could substitute crypto that sends the key or plaintext out. Because we terminate the outer TLS, we are structurally in a position to do this — so the guarantee against us is only as strong as the protection of code delivery. This is the well-known criticism of in-browser crypto (and of services like Mega). We address it in layers:
| Measure | What it addresses |
|---|---|
| Root of trust outside WGP | Serve the bootstrap HTML and the crypto bundle via passthrough (zone 3) or from an ENISA origin with its own cert. Whatever carries trust must not pass through a layer we can modify. |
| SRI (Subresource Integrity) | The bootstrap HTML pins the bundle hash; the browser rejects a tampered bundle. (But the hash itself must come from a trusted source — see above.) |
| Code signing / PWA / extension | Stronger binding to specific code than SRI alone. |
| Transparency | Published hashes, reproducible build, independent audit. |
| Native / M2M client | No JS delivery → strongest. ENISA controls the binary. |
Even with SRI, the bootstrap document carrying that hash must arrive with integrity — if we serve it, we could change the hash. So the root of trust must sit outside WGP (ENISA origin or passthrough). The strongest guarantee comes from a native/installed client. And metadata (sizes, timing, paths) we see regardless → traffic analysis; mitigate with padding and uniform endpoints.
When passthrough and when E2EE
| Property | Passthrough (zone 3) | Termination + E2EE payload |
|---|---|---|
| Integration in ENISA's app | none | required (client-side crypto) |
| L7 protection | L3/L4 + profile only | full L7 (over the envelope) |
| Do we see content? | no (we don't terminate) | no (E2EE) — even though we terminate |
| Do we see metadata? | L3/L4 | HTTP structure + sizes + JA4 |
| Suitable for | when ENISA cannot change the app | the data-submission site; when it can integrate |
The origin always authorizes — holding the key is the proof of authorization. This option is also a candidate for resolving open point 5 (machine-to-machine endpoint) at the application layer. For CRA: we terminate (full L7) yet provably cannot read the content — stronger data minimization.
13Solution components
Components already in place and components that will be part of the solution.
| Component | Benefit | Status |
|---|---|---|
| Multi-layer limits (IP/ASN/cat/CC/continent + combos) | Rules on every layer | have |
| Tiered PoW / captcha | Dumb and smart attacks | have |
| Sanitizing client trust headers | OK cannot be spoofed | have |
| Separate domain/cert for CDN | No coalescing of the sensitive host | have |
| KO up to the edge (global) | Attacker caught most cheaply | part of solution |
| Rate-cap on OK too | A hijacked OK cannot flood the origin | part of solution |
| State written only by ALB (AL4 read-only) | Single source of truth | part of solution |
| Block page from CDN cache (multilingual) | Attacking it does not matter | part of solution |
| Demote OK → unknown on anomaly | ASN/geo jump → re-auth | part of solution |
| mTLS / API key for machine-to-machine | Hard anchor for data submission | to be decided |
14CRA alignment (EU 2024/2847)
The model is a direct security-by-design argument: in the sensitive zone WGP does not terminate → does not process content = data minimization and attack-surface reduction (Annex I).
| Date | What applies |
|---|---|
| 11 Sep 2026 | Reporting (Art. 14): report actively exploited vulnerabilities and severe incidents to ENISA/CSIRT within 24 h. The nearest action point. |
| 11 Dec 2027 | Full application: essential requirements, conformity assessment, CE marking, documentation. |
Product vs. service is a matter of interpretation for the legal/ENISA channel. 300 s data retention = demonstrable minimization; zone 3 content is not logged (passthrough).
15Our rules (specification)
Binding rules by which we build the solution. One point remains open for agreement (below).
| # | Rule | Specification |
|---|---|---|
| 1 | XDP at the edge | We deploy XDP on anycast POPs — volumetric protection and JA4T extraction from the SYN. The kernel code passes internal review (Jakub). Binding. |
| 2 | Transport POP → box | Between the anycast points and the dedicated box: public transit with an allowlist (WGP IPs only). From the dedicated box we offer both public transit and a private backbone — per agreement. |
| 3 | Protocol of the protected portal | We enforce HTTP/2 only (TCP); HTTP/1.1 and HTTP/3 disabled. Stable fingerprint and JA4T requires TCP. The machine-to-machine endpoint is handled separately (point 5 below). |
| 4 | Profile — phasing | Phase 1: IP + JA4. Phase 2: + JA4T. Target state = all signals. |
| 5 | Mobile in zone 3 | Strict — IP change = block + re-login. No exceptions. |
Machine-to-machine endpoint (data-submission site): mTLS / API key at the origin as a hard anchor — still open. Until agreed, this endpoint runs in the same mode as a human (profile IP + JA4 (+JA4T)).
16What we can protect (capability overview)
ENISA asked about captcha — yes, and it is just one tool among many. Below is an overview of the layers, signals and actions we can decide on. Everything is configurable per domain (Anygma profile) and freely combinable; not everything is enabled by default — the exact set is tuned per deployment.
Captcha is one challenge option. We handle most traffic invisibly (background bot detection) or via risk-tiered PoW; a visible captcha we deploy selectively (datacenter / VPN / Tor / suspicion), not blanket — for UX reasons. A challenge is just one of many possible outputs of a rule.
Layers — where we decide
| Layer | What we can do there |
|---|---|
| L3/L4 · XDP, AL4 | Volumetric protection, SYN handling, JA4T from SYN, connection limits, anycast absorption |
| TLS · ACT / ACU | JA4, HTTP/2 fingerprint, QUIC fingerprint (after termination) |
| L7 · ALB / AL7 | JA4H, headers, path, method, WAF, challenges, OK/KO/unknown decisioning |
| Cache · AVC | Microcache, static, origin offload, block page from CDN |
| Challenge · ATP | Invisible detection, PoW (tiered), captcha |
Signals and parameters (hundreds of combinations)
| Category | Parameters we decide on |
|---|---|
| Network / identity | IP, prefix, ASN, country (GeoCC), continent, IP category (datacenter / residential / VPN / Tor / search-engine), GeoASN |
| Fingerprint | JA4, JA4T (TCP/SYN), JA4H (HTTP), HTTP/2 fingerprint, QUIC, JA4 composite |
| Reputation (feeds) | Spamhaus DROP / ASN-DROP, abuse.ch Feodo / ThreatFox, GreyNoise RIOT / Business allowlist |
| HTTP attributes | User-Agent (blacklist / bad / old / AI / missing / short / whitespace), Referrer, Content-Type, method, path, headers, cookies (login) |
| Behavior | rate (req/min), concurrency, connection count, 404/301 patterns (Behavior Shield), honeypot, behavioral baseline |
| Application | CMS autodetection (WordPress / Drupal / Magento / PrestaShop / Joomla) → tailored rules; WebSocket; combo engine (combining dimensions) |
Limits — per dimension and in combination
We can limit and block per IP / ASN / country / continent / category / path and their combinations with JA4 / JA4T and other parameters. Metrics: connection count, concurrency, request count (L7), rate. That yields on the order of hundreds of possible rules per domain.
Actions — what we do with it
| Action | Variants |
|---|---|
| PASS | passes unchanged |
| LOG | log only (monitoring, no action) |
| RATE-LIMIT | per any dimension above |
| CHALLENGE | invisible detection / PoW (tiered) / captcha |
| BLOCK | drop, 4xx, block page from CDN cache |
Everything is configured per domain via the Anygma profile and is combinable (combo engine). We log traffic to OpenSearch (18-month retention, correlation across layers via the trace UUID). The exact enabled rule set is a matter of deployment and agreement with ENISA.
17Version history
| Version | Date | Change |
|---|---|---|
| 1.2.0 | 2026-06-28 | Expanded the E2EE/HPKE alternative (humans and machines in detail, code-delivery weakness); added the capability overview (layers, signals, limits, actions). |
| 1.1.0 | 2026-06-28 | Added alternative: termination + E2EE payload (HPKE) — one envelope for humans and M2M, OPAQUE for password users, comparison with passthrough. |
| 1.0.0 | 2026-06-28 | First released version for ENISA. |
Draft for approval. No production change. Once approved → technical specification, then configuration (versioned from 1.0.0, staged rollout, Marek and Ivan notified).