NAME

PAGI::Server::Compliance - HTTP/1.1, WebSocket, and Security Compliance Documentation

DESCRIPTION

This document details the compliance testing results for PAGI::Server against HTTP/1.1 (RFC 7230/7231), WebSocket (RFC 6455), and common security attack vectors.

PAGI::Server demonstrates full HTTP/1.1 compliance, strong security posture, stable resource management, and 71% WebSocket RFC 6455 compliance.

TEST ENVIRONMENT

  • Server: PAGI::Server with IO::Async and EV backend

  • Platform: macOS Darwin / Linux

  • Event Loop: EV with kqueue (macOS) or epoll (Linux)

  • Test Date: December 2025

RESULTS SUMMARY

+---------------------------+-------+--------+--------+
| Category                  | Tests | Passed | Failed |
+---------------------------+-------+--------+--------+
| HTTP/1.1 Compliance       |    10 |     10 |      0 |
| Slow HTTP Attacks         |     4 |      4 |      0 |
| Concurrent Attack+Traffic |     1 |      1 |      0 |
| Request Smuggling         |     6 |      6 |      0 |
| nikto Scanner             |     4 |      4 |      0 |
| Protocol Fuzzing          |    49 |     49 |      0 |
| Memory/Resource Leaks     |     4 |      4 |      0 |
| WebSocket (Autobahn)      |   301 |    215 |     86 |
+---------------------------+-------+--------+--------+

HTTP/1.1 COMPLIANCE

PAGI::Server is fully compliant with RFC 7230 (HTTP/1.1 Message Syntax and Routing) and RFC 7231 (HTTP/1.1 Semantics and Content).

Compliance Tests

+------------------------------+----------+--------+--------+
| Test                         | Expected | Actual | Status |
+------------------------------+----------+--------+--------+
| Normal request               |      200 |    200 | PASS   |
| HTTP/1.1 missing Host        |      400 |    400 | PASS   |
| HTTP/1.0 no Host             |      200 |    200 | PASS   |
| Content-Length: abc          |      400 |    400 | PASS   |
| Content-Length: -1           |      400 |    400 | PASS   |
| Content-Length: overflow     |      413 |    413 | PASS   |
| Content-Length: with spaces  |      400 |    400 | PASS   |
| URI > 8KB                    |      414 |    414 | PASS   |
| Header > 8KB                 |      431 |    431 | PASS   |
| CL+TE conflict               |      200 |    200 | PASS   |
+------------------------------+----------+--------+--------+

RFC 7230 Section 5.4 - Host Header

HTTP/1.1 requests without a Host header correctly return 400 Bad Request. HTTP/1.0 requests without Host are allowed per specification.

RFC 7230 Section 3.3.3 - Message Body Length

Transfer-Encoding takes precedence over Content-Length when both are present, providing protection against HTTP request smuggling attacks.

SECURITY TESTING

Slow HTTP Attack Resistance

PAGI::Server's async architecture provides natural resistance to slow HTTP attacks. Unlike pre-fork servers (Apache, Starman), slow connections don't consume worker processes.

Slowloris Attack (Slow Headers)

Test: 500 connections sending headers very slowly (10 second intervals)
Duration: 30 seconds
Result: Service remained fully available
Status: PASS

Slow POST Attack (Slow Body)

Test: 500 connections sending POST body very slowly
Duration: 30 seconds
Result: Server actively closed slow connections
Status: PASS

Slow Read Attack

Test: 500 connections reading responses very slowly (32 bytes per 5 seconds)
Duration: 30 seconds
Result: Service remained fully available
Status: PASS

Concurrent Attack + Normal Traffic

Test: Slowloris attack while serving normal traffic
Attack: 500 slow connections
Normal traffic results:
  - Requests/sec: 3,843
  - Total requests: 76,963
  - Errors: 0
  - p99 latency: 33ms
Status: PASS

HTTP Request Smuggling

PAGI::Server is NOT VULNERABLE to HTTP Request Smuggling attacks.

CL.TE and TE.CL Attack Vectors

Transfer-Encoding always takes precedence over Content-Length per RFC 7230 Section 3.3.3. Smuggled data is rejected with 400 Bad Request.

Transfer-Encoding Obfuscation

All obfuscation techniques are handled safely:

+-------------------------+-------------------+
| Variant                 | Result            |
+-------------------------+-------------------+
| Normal chunked          | TE recognized     |
| CHUNKED (uppercase)     | TE recognized     |
| Space before colon      | TE recognized     |
| Tab after colon         | TE recognized     |
| Lowercase header        | TE recognized     |
| Line folding (obs-fold) | TE recognized     |
| Null byte in value      | 400 Rejected      |
| Vertical tab            | 400 Rejected      |
| Form feed               | 400 Rejected      |
+-------------------------+-------------------+

Duplicate Content-Length Headers

+----------------------------------+---------------+
| Test                             | Result        |
+----------------------------------+---------------+
| Duplicate CL (same values)       | 400 Rejected  |
| Duplicate CL (different values)  | 400 Rejected  |
| CL with leading zeros            | 200 Accepted  |
| CL with + sign                   | 400 Rejected  |
+----------------------------------+---------------+

Protocol Fuzzing

49/49 fuzzing tests passed. Server never crashed or became unresponsive.

Malformed Request Lines

Empty requests, missing path/version, invalid HTTP version, null bytes in method/path all correctly return 400 Bad Request.

Malformed Headers

Headers without colon, empty header names, null bytes in headers all correctly return 400 Bad Request. Excessive headers (>100) return 431.

Extreme Sizes

+----------------------+-------------------------+
| Test                 | Result                  |
+----------------------+-------------------------+
| 1MB URI              | Timeout (protected)     |
| 1MB header value     | Timeout (protected)     |
| 100KB single header  | 431 Header Too Large    |
+----------------------+-------------------------+

Binary/Random Data

Random bytes, all nulls, and mixed binary + HTTP data are handled safely without crashes.

nikto Security Scanner

No critical or high-severity vulnerabilities found. Security headers (X-Frame-Options, X-Content-Type-Options) are application-level responsibilities and can be added using the PAGI::Middleware::SecurityHeaders middleware.

RESOURCE MANAGEMENT

Memory Usage Under Sustained Load

+-----------+------------+--------+-------------+
| Requests  | RSS (KB)   | Growth | Growth/100K |
+-----------+------------+--------+-------------+
| Initial   |     28,392 |      - |           - |
| 100K      |     30,048 | +1,656 |      +1,656 |
| 200K      |     31,048 | +2,656 |      +1,000 |
| 500K      |     32,736 | +4,344 |        +563 |
| 1,000K    |     34,944 | +6,552 |        +440 |
+-----------+------------+--------+-------------+

Memory growth of ~6.5 MB over 1 million requests (~6.5 bytes/request) is normal Perl memory allocator behavior, not a leak. Growth rate decreases over time.

File Descriptor Management

+--------------------------------+--------------------+
| Phase                          | File Descriptors   |
+--------------------------------+--------------------+
| Initial (idle)                 |                 35 |
| During load (100 concurrent)   |                135 |
| After load (idle)              |                 35 |
| After 1M requests              |                 35 |
+--------------------------------+--------------------+

File descriptors return to baseline after connections close.

Connection Object Cleanup

WebSocket frame parsers, connection objects after exceptions, and connections after partial responses are all properly cleaned up.

WEBSOCKET COMPLIANCE (RFC 6455)

Tested against the Autobahn Testsuite v25.10.1, the industry-standard WebSocket conformance test suite.

Results Summary

+---------------+-------+--------------------------------+
| Behavior      | Count | Description                    |
+---------------+-------+--------------------------------+
| OK            |   202 | Passed                         |
| NON-STRICT    |    10 | Minor deviations, acceptable   |
| INFORMATIONAL |     3 | Informational only             |
| FAILED        |    86 | Failed tests                   |
| UNIMPLEMENTED |   216 | Compression (intentional)      |
+---------------+-------+--------------------------------+

Pass rate (excluding compression): 215/301 (71%)

Results by Category

+----------+----------------------+----+--------+---------------------------+
| Category | Description          | OK | Failed | Notes                     |
+----------+----------------------+----+--------+---------------------------+
|        1 | Text/Binary Messages | 16 |      0 | All pass                  |
|        2 | Ping/Pong            | 11 |      0 | Control frame size OK     |
|        3 | Reserved Bits        |  7 |      0 | RSV bits validated        |
|        4 | Opcodes              | 10 |      0 | Reserved opcodes rejected |
|        5 | Fragmentation        | 14 |      6 | Edge cases                |
|        6 | UTF-8 Handling       | 99 |     42 | Streaming validation      |
|        7 | Close Handling       | 30 |      1 | Close codes validated     |
|        9 | Limits/Performance   | 14 |     37 | Large messages (>64KB)    |
|       10 | Misc                 |  1 |      0 | All pass                  |
|    12-13 | Compression          |  0 |      0 | 216 unimplemented         |
+----------+----------------------+----+--------+---------------------------+

Validations Implemented in PAGI::Server

  • RSV Bits (Category 3)

    Validates RSV1-3 must be 0 when no extensions are negotiated. Closes connection with 1002 Protocol Error if non-zero.

  • Reserved Opcodes (Category 4)

    Rejects opcodes 3-7 and 11-15 with 1002 Protocol Error.

  • Control Frame Size (Category 2)

    Rejects ping/pong/close frames with payload >125 bytes per RFC 6455 Section 5.5.

  • Close Code Validation (Category 7)

    Validates close codes per RFC 6455 Section 7.4.1:

    Valid: 1000-1003, 1007-1011, 3000-4999
    Invalid: 0-999, 1004-1006, 1012-2999, 5000+
  • Close Frame Format

    Rejects close frames with 1-byte payload (must be 0 or >=2 bytes).

  • Close Reason UTF-8

    Validates close reason is valid UTF-8.

Known Limitations

These limitations are in the Protocol::WebSocket library, not PAGI::Server:

  • UTF-8 Streaming (Category 6)

    Invalid UTF-8 in fragmented messages is validated at message end, not per-fragment.

  • Fragmentation Edge Cases (Category 5)

    Complex interleaved control/data frame scenarios.

  • Large Messages (Category 9)

    Default 64KB limit. Configurable via max_ws_frame_size:

    my $server = PAGI::Server->new(
        max_ws_frame_size => 16 * 1024 * 1024,  # 16MB
    );

Compression (Categories 12-13)

PAGI does not implement WebSocket compression (permessage-deflate per RFC 7692). This is optional and intentionally not supported. 216 tests are marked UNIMPLEMENTED, which is the correct behavior.

IMPLEMENTED PROTECTIONS

Request Parsing

  • RFC 7230 Section 5.4: HTTP/1.1 Host header requirement - returns 400

  • RFC 7230 Section 3.3.3: TE takes precedence over CL (smuggling protection)

  • Request line size limit (8KB default) - returns 414

  • Header size limit (8KB default) - returns 431

  • Header count limit (100 default) - returns 431

  • Content-Length validation - returns 400/413

  • Duplicate Content-Length rejection - returns 400

  • Control character rejection in headers - returns 400

WebSocket

  • Frame size limit (--max-ws-frame-size, default 64KB)

  • Receive queue limit (--max-receive-queue, default 1000 messages)

  • RSV bits validation (closes with 1002)

  • Reserved opcode rejection (closes with 1002)

  • Control frame size validation (closes with 1002)

  • Close code validation (closes with 1002)

Resource Protection

  • Connection idle timeout (60 seconds default)

  • Graceful shutdown with timeout (30 seconds default)

  • Listener backlog limit (2048 default)

ARCHITECTURE ADVANTAGES

PAGI::Server's async architecture provides inherent protection against certain attack classes:

+------------------+--------------+-------------------------+
| Server           | Architecture | Slowloris Vulnerability |
+------------------+--------------+-------------------------+
| Apache           | Pre-fork     | HIGH - Workers exhaust  |
| Starman          | Pre-fork     | HIGH - Workers exhaust  |
| Gazelle          | Pre-fork     | HIGH - Workers exhaust  |
| PAGI::Server     | Async        | LOW - Event loop OK     |
| Uvicorn (Python) | Async        | LOW - Similar to PAGI   |
| Nginx            | Event-driven | LOW - Designed for this |
+------------------+--------------+-------------------------+

RECOMMENDATIONS

Production Deployment

For production deployments, consider:

  1. Use a reverse proxy (nginx, HAProxy) for TLS termination, rate limiting, and per-IP connection limits.

  2. Enable the PAGI::Middleware::SecurityHeaders middleware for security headers.

  3. Set appropriate limits for your use case:

    my $server = PAGI::Server->new(
        timeout           => 60,      # Connection idle timeout
        max_header_size   => 8192,    # 8KB header limit
        max_header_count  => 100,     # Max headers per request
        max_body_size     => 10_000_000,  # 10MB body limit
        max_ws_frame_size => 1_000_000,   # 1MB WebSocket frames
        shutdown_timeout  => 30,      # Graceful shutdown timeout
    );

Performance Optimization

For optimal performance on macOS, set the EV backend to kqueue:

LIBEV_FLAGS=8 pagi-server --loop EV --app myapp.pl

SEE ALSO

PAGI::Server - The main server module

PAGI::Middleware::SecurityHeaders - Security headers middleware

RFC 7230 - HTTP/1.1 Message Syntax and Routing

RFC 7231 - HTTP/1.1 Semantics and Content

RFC 6455 - The WebSocket Protocol

Autobahn Testsuite - WebSocket test suite

AUTHOR

John Napiorkowski <jjnapiork@cpan.org>

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.