sgcIndy 2026.6: Server Security Hardening for TLS, TCP and HTTP

· Releases

sgcIndy 2026.6 turns its attention to the server side. This release adds a set of opt-in security hardening options to the TLS, TCP and HTTP server components, closing a number of well-known attack classes that the underlying Indy code never guarded against: a fail-open certificate-verification callback, slow-drip Slowloris connections, unbounded request bodies and headers, and HTTP request smuggling. It also ships the first ML-KEM-768 post-quantum key-encapsulation primitives.

Every new protection defaults to the previous behaviour, so existing applications are unaffected until you switch them on. This post walks through each one with ready-to-paste Delphi snippets.

Hardened TLS servers

The most important fix is in the OpenSSL peer-verification callback. In stock Indy, the verify callback can fail open: when a server requests a client certificate, the final decision was driven by the user callback and ignored OpenSSL's own verdict, so a callback that returned success accepted an expired, self-signed or untrusted certificate. The new TIdSSLOptions.StrictVerify flag enforces the OpenSSL result, so a custom OnVerifyPeer handler can only further restrict the decision, never relax it.

Three more flags wire up OpenSSL options that the headers already declared but the library never applied: DisableCompression mitigates CRIME, DisableRenegotiation blocks client-initiated renegotiation (a CPU-asymmetric DoS) on OpenSSL 1.1.0h and later, and ServerCipherPreference makes the server's cipher order win the negotiation instead of the client's.

uses
  IdHTTPServer, IdSSLOpenSSL;

var
  oServer: TIdHTTPServer;
  oSSL: TIdServerIOHandlerSSLOpenSSL;
begin
  oServer := TIdHTTPServer.Create(nil);
  oSSL := TIdServerIOHandlerSSLOpenSSL.Create(oServer);

  oSSL.SSLOptions.CertFile    := 'server.pem';
  oSSL.SSLOptions.KeyFile     := 'server.key';
  oSSL.SSLOptions.SSLVersions := [sslvTLSv1_2, sslvTLSv1_3];

  // opt-in hardening, every flag defaults to False
  oSSL.SSLOptions.StrictVerify           := True;  // enforce the OpenSSL verdict
  oSSL.SSLOptions.DisableCompression     := True;  // CRIME
  oSSL.SSLOptions.DisableRenegotiation   := True;  // renegotiation DoS
  oSSL.SSLOptions.ServerCipherPreference := True;  // server picks the cipher

  oServer.IOHandler := oSSL;
  oServer.Active := True;
end;

Set VerifyMode to [sslvrfPeer, sslvrfFailIfNoPeerCert] together with StrictVerify when you want mutual TLS that actually rejects an unknown client certificate.

Stopping Slowloris

A Slowloris client opens many connections and dribbles one byte at a time, never completing the request, to pin every worker thread. The natural defence looks like a read timeout, but there is a subtlety worth knowing: Indy's ReadTimeout is an inactivity timeout. Each byte that arrives resets it, so a client that sends a byte every few seconds keeps the connection alive forever.

2026.6 adds a real total read deadline to TIdIOHandler via SetReadDeadline. It is checked on every read, so it fires even while bytes keep trickling in. The HTTP server wires this up automatically through the new RequestReadTimeout property, which bounds the time to receive the request line and headers and resets per keep-alive request.

uses
  IdHTTPServer;

var
  oServer: TIdHTTPServer;
begin
  oServer := TIdHTTPServer.Create(nil);
  oServer.DefaultPort := 8080;

  // close any client that has not finished sending the request
  // line and headers within 5 seconds, even if it keeps dripping bytes
  oServer.RequestReadTimeout := 5000;

  oServer.Active := True;
end;

For a custom TIdTCPServer you can apply the same deadline by hand around your read loop. Pass 0 to clear it.

procedure TForm1.ServerExecute(AContext: TIdContext);
var
  vLine: string;
begin
  // bound the whole request to 5 seconds of total read time
  AContext.Connection.IOHandler.SetReadDeadline(5000);
  try
    vLine := AContext.Connection.IOHandler.ReadLn;
    // ... handle the request ...
  finally
    AContext.Connection.IOHandler.SetReadDeadline(0);
  end;
end;

Request limits for HTTP servers

An HTTP server that reads whatever a client declares is an easy memory-exhaustion target. A single request can announce Content-Length: 2000000000 and the server will try to buffer two gigabytes, or stream an endless chunked body, or send millions of header bytes. 2026.6 adds three caps and a smuggling check to TIdCustomHTTPServer.

MaxRequestBodySize caps the Content-Length and the summed chunked body and replies 413 Payload Too Large when it is exceeded. MaxHeaderTotalSize caps the total header bytes and replies 431 Request Header Fields Too Large. StrictRequestParsing rejects requests that are ambiguous on purpose, such as a message that carries both Content-Length and Transfer-Encoding: chunked (the classic request-smuggling vector) or a negative chunk size, replying 400 Bad Request. The chunked trailer-header loop is now bounded as well, so an attacker can no longer hold a connection open with an infinite stream of trailer lines.

oServer.MaxRequestBodySize := 10 * 1024 * 1024;  // 10 MB, else 413
oServer.MaxHeaderTotalSize := 64 * 1024;         // 64 KB, else 431
oServer.StrictRequestParsing := True;            // reject CL+TE smuggling, else 400

At the raw TCP layer, TIdIOHandler.MaxInputBufferSize bounds the cumulative input buffer for any protocol built on the IOHandler, which stops a length-prefixed read or an oversized line from growing the buffer without limit.

// inside OnConnect / OnExecute of any Indy server
AContext.Connection.IOHandler.MaxInputBufferSize := 1024 * 1024;  // 1 MB cap

Opt-in by design

None of these options change the default behaviour. The fields all default to off (0 for the size and timeout limits, False for the boolean flags), so a project that upgrades to 2026.6 behaves exactly as it did on 2026.5. You enable precisely the protections your deployment needs, and the same code compiles across Delphi 7 through RAD Studio 13 and Free Pascal.

Also in this release

2026.6 introduces the first ML-KEM-768 post-quantum encapsulation and decapsulation primitives, available on OpenSSL 3.5 and later. They expose a simple TBytes API so you can layer a post-quantum key-encapsulation step into a hybrid handshake alongside the classical ECDH exchange.

On the build side, C++Builder package compilation no longer fails with the MSBuild error MSB1008 when RAD Studio is installed in a path that contains spaces. The DCC_BpiOutput parameter is now quoted.

Upgrading

2026.6 is a drop-in upgrade. There are no breaking changes and nothing to migrate, because every new protection is opt-in. Review the snippets above and enable the options that fit your server.

sgcIndy is free. Download the latest build from esegece.com/products/sgcindy/download.

Questions, feedback or help hardening your server? Get in touch, you will get a reply from the people who wrote the code.