On-Prem Frontend Security Hardening Guide

Overview

This guide documents five hardening updates to the HAProxy edge configuration for on-premise AgileSec platforms. Each hardening control is enforced uniformly across all backend services from a single point as every client response passes through HAProxy. Together, these hardening updates reduce AgileSec’s browser-facing attack surface, eliminate server fingerprinting, and remove legacy TLS exposure.

Configuration

Remediation

1

Cross-Origin Resource Sharing (CORS)

Configure CORS at the HAProxy layer to allow browser requests only from explicitly approved origins.

2

HTTP Security Headers

Configure HTTP Security Headers to protect from browser-based vulnerabilities with X-Frame-Options, X-Content-Type Options, and Strict-Transport-Security.

3

Content Security Policy (CSP)

Configure CSP header to restrict which sources the browser is permitted to load resources from, protecting against cross-site scripting (XSS) and data injection attacks.

4

Server Technology Disclosure

Configure removal of server technology disclosing headers at the HAProxy layer to ensure they are stripped before reaching any client.

5

TLS 1.3 Configuration Update

Configure HAProxy SSL configuration to enforce TLS 1.3 exclusively and remove the TLS 1.2 attack surface entirely.

Deployment port 8443 note: This guide uses both the standard HTTPS listener, bind :443 ssl ..., and examples without an explicit port. If HTTPS is exposed directly on port 8443, configure the client-facing HAProxy frontend with bind :8443 ssl ... and add :8443 to all validation URLs. For example: <https://<agilesec-fqdn>>:8443/health-check.

Note: If using non-privileged port 8443, do not use sudo with ./manage.sh.

Prerequisites

Ensure the following prerequisites are met.

Set Environment Variables

Set environment variables to easily copy and paste instructions.

  1. agilesec_install_dir: the location where AgileSec will be installed to.

export agilesec_install_dir=</path/to/installation>

Backup Frontend HAProxy Configuration

Important: Make a backup of your frontend node HAProxy configuration before making any changes:

cp $agilesec_install_dir/services/haproxy/haproxy.cfg $agilesec_install_dir/services/haproxy/haproxy.cfg.bak

Cross-Origin Resource Sharing (CORS) Configuration

Configure CORS at the HAProxy layer to allow browser requests only from explicitly approved origins.

In $agilesec_install_dir/services/haproxy/haproxy.cfg, add the following directives inside frontend https_frontend_local, after the existing path ACL definitions and before the use_backend rules:

frontend https_frontend_local
    bind :443 ssl ...
    ...

    # CORS policy
    # Origins must include scheme and optional port; do not include a trailing slash.
    acl cors_allowed_origin hdr(Origin) -i https://<agilesec-fqdn>
    acl cors_origin_present hdr(Origin) -m found
    acl cors_preflight method OPTIONS

    # Store an approved Origin for use in response headers.
    http-request set-var(txn.cors_origin) hdr(Origin) if path_api !path_ingestion cors_allowed_origin

    # Reject requests with a supplied but unapproved Origin.
    # Requests without an Origin header, such as health checks and server-to-server
    # traffic, are not affected.
    http-request deny deny_status 403 if path_api !path_ingestion cors_origin_present !cors_allowed_origin

    # Respond to approved browser preflight requests without forwarding them upstream.
    http-request return status 204 \
      hdr Access-Control-Allow-Origin "%[var(txn.cors_origin)]" \
      hdr Access-Control-Allow-Credentials "true" \
      hdr Access-Control-Allow-Methods "GET,HEAD,PUT,PATCH,POST,DELETE" \
      hdr Access-Control-Allow-Headers "%[req.hdr(Access-Control-Request-Headers)]" \
      hdr Vary "Origin, Access-Control-Request-Headers" \
      if path_api !path_ingestion cors_preflight cors_allowed_origin

    # Add CORS headers to approved cross-origin responses.
    http-response set-header Access-Control-Allow-Origin "%[var(txn.cors_origin)]" if { var(txn.cors_origin) -m found }
    http-response set-header Access-Control-Allow-Credentials "true" if { var(txn.cors_origin) -m found }
    http-response add-header Vary "Origin" if { var(txn.cors_origin) -m found }

Note: Replace https://<agilesec-fqdn> with your actual external FQDN (e.g. https://agilesec.kf-agilesec.com). An FQDN consists of the protocol, hostname, and optional port. Do not add path components or trailing slashes.

Security requirement: Do not configure a wildcard (*) origin when Access-Control-Allow-Credentials is enabled. Use a restricted allowlist of exact origins.

  1. Validate and reload HAProxy:

cd $agilesec_install_dir/services/haproxy
./haproxy -c -f haproxy.cfg
cd $agilesec_install_dir/scripts
sudo ./manage.sh restart haproxy

Note: HAProxy configuration validation must show no alerts before it is reloaded.

Note: If using non-privileged port 8443, do not use sudo with ./manage.sh.

  1. Verify an approved preflight request:

    curl -ik -X OPTIONS https://<agilesec-fqdn>/health-check \
      -H 'Origin: https://<agilesec-fqdn>' \
      -H 'Access-Control-Request-Method: GET' \
      -H 'Access-Control-Request-Headers: Authorization,Content-Type'
    

    Expected response:

    HTTP/1.1 204 No Content
    Access-Control-Allow-Origin: https://<agilesec-fqdn>
    Access-Control-Allow-Credentials: true
    
  2. Repeat the preflight request with an unapproved Origin. The expected result is HTTP 403.

    curl -ik -X OPTIONS https://<agilesec-fqdn>/health-check \
      -H 'Origin: https://example.invalid' \
      -H 'Access-Control-Request-Method: GET' \
      -H 'Access-Control-Request-Headers: Authorization,Content-Type'
    

Result: Approved origins receive the required CORS headers and preflight response. Requests with an unapproved supplied origin are rejected.

  1. Verify an approved actual request:

    curl -ik https://<agilesec-fqdn>/health-check \
      -H 'Origin: https://<agilesec-fqdn>'
    

    Expected response:

    HTTP 200
    Access-Control-Allow-Origin: https://<agilesec-fqdn>
    Access-Control-Allow-Credentials: true
    

    There should be no wildcard *.

  2. Repeat the actual request with an unapproved Origin. The expected result is HTTP 403.

    curl -ik https://<agilesec-fqdn>/health-check \
      -H 'Origin: https://example.invalid'
    

Result: Approved origins receive the required CORS headers and preflight response. Requests with an unapproved supplied origin are rejected.

HTTP Security Headers Configuration

Configure HTTP Security Headers to protect from browser-based vulnerabilities with X-Frame-Options, X-Content-Type Options, and Strict-Transport-Security.

Add the following directives to the https_frontend_local section in $agilesec_install_dir/services/haproxy/haproxy.cfg on frontend nodes:

frontend https_frontend_local
    bind :443 ssl ...
    ...

    # Security headers
    http-request set-var(txn.file_path) path

    http-response set-header X-Frame-Options                  "SAMEORIGIN"
    http-response set-header X-Content-Type-Options           "nosniff"
    http-response set-header Strict-Transport-Security        "max-age=31536000; includeSubDomains; preload"

    # Force correct MIME type and download behavior for file download endpoints
    http-response set-header Content-Type                     "application/zip" if { var(txn.file_path) -m sub /v1/file-upload/local/ }
    http-response set-header Content-Disposition              "attachment" if { var(txn.file_path) -m sub /v1/file-upload/local/ }
    http-response set-header Content-Type                     "application/octet-stream" if { var(txn.file_path) -m sub /v1/platform-sensors/remote-sensor/ }

Note: These directives must be placed inside a frontend or listen section. HAProxy does not allow http-response rules in the defaults section. http-request directive must be added before the http-response directives

Validate and reload HAProxy:

cd $agilesec_install_dir/services/haproxy
./haproxy -c -f haproxy.cfg
cd $agilesec_install_dir/scripts
sudo ./manage.sh restart haproxy

Note: HAProxy config validation must show no alerts.

Note: If using non-privileged port 8443, do not use sudo with ./manage.sh.

Verify headers are present in the response:

curl -I https://<agilesec-fqdn>/health-check

Expected response headers:

x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
strict-transport-security: max-age=31536000; includeSubDomains; preload

Result: X-Frame-Options, X-Content-Type-Options, and Strict-Transport-Security should appear in the response headers.

Content Security Policy (CSP) Configuration

A CSP header restricts which sources the browser is permitted to load resources from, protecting against cross-site scripting (XSS) and data injection attacks.

On frontend nodes, add the following http-response directives to the https_frontend_local section in $agilesec_install_dir/services/haproxy/haproxy.cfg, after the existing security headers:

frontend https_frontend_local
    ...

    # Content Security Policy
    http-response set-header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' wss://<agilesec-fqdn>;"

Note: Replace <agilesec-fqdn> with your actual external FQDN for WebSocket connections (e.g. wss://agilesec.kf-agilesec.com). The unsafe-inline and unsafe-eval directives are required to support the platform frontend framework.

Note: If using non-privileged port 8443, please use wss://agilesec.kf-agilesec.com:8443 for WebSocket connections.

Validate and reload HAProxy:

cd $agilesec_install_dir/services/haproxy
./haproxy -c -f haproxy.cfg
cd $agilesec_install_dir/scripts
sudo ./manage.sh restart haproxy

Note: HA proxy config validation must show no alerts.

Note: If using non-privileged port 8443, do not use sudo with ./manage.sh.

After reloading HAProxy, verify the CSP header is returned by inspecting the response from the sign-in page:

curl -I https://<agilesec-fqdn>/signin

Expected response header:

content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline'
    'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;
    connect-src 'self' wss://<agilesec-fqdn>;

Result: The content-security-policy header should appear in the response confirming the directive is applied at the HAProxy layer.

Server Technology Disclosure Configuration

By default, backend applications include HTTP response headers identifying server technology in use. Two headers are of particular concern:

  • x-powered-by: Express or x-powered-by: Next.js – directly identifies the application framework

  • server: – identifies the underlying web server software and version

Removing these headers at the HAProxy layer ensures they are stripped before reaching any client.

Locate the frontend section in your $agilesec_install_dir/services/haproxy/haproxy.cfg on frontend nodes handling client-facing traffic. Add the following two lines inside the frontend section section to strip server fingerprinting headers:

frontend https_frontend_local
    bind *:443 ssl ...
    ...

    # Strip server fingerprinting headers
    http-response del-header x-powered-by
    http-response del-header server

    default_backend ...

Note: These directives must be placed inside a frontend or listen section, not in the defaults section.

Validate and reload HAProxy:

cd $agilesec_install_dir/services/haproxy
./haproxy -c -f haproxy.cfg
cd $agilesec_install_dir/scripts
sudo ./manage.sh restart haproxy

Verify the headers are removed by inspecting the response from health-check and signin URLs:

curl -I https://<agilesec-fqdn>/health-check
curl -I https://<agilesec-fqdn>/signin

Result: The x-powered-by and server headers should no longer appear in the responses. All other headers remain unaffected.

TLS 1.3 Configuration Update

Current HAProxy SSL configuration already disables SSLv3, TLS 1.0, and TLS 1.1. However, TLS 1.2 is still permitted for broader compatibility. To enforce TLS 1.3 exclusively and remove the TLS 1.2 attack surface entirely, make the following configuration updates to $agilesec_install_dir/services/haproxy/haproxy.cfgon frontend nodes:

  1. Add no-tlsv12 to both the ssl-default-bind-options and ssl-default-server-options lines.

  2. Remove the ssl-default-bind-ciphers and ssl-default-server-ciphers lines – these are TLS 1.2 cipher lists and have no effect once TLS 1.2 is disabled.

global
    ...
    # TLS 1.3 only – no-tlsv12 added, TLS 1.2 cipher lists removed
    ssl-default-bind-options    no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

    ssl-default-server-options  no-sslv3 no-tlsv10 no-tlsv11 no-tlsv12 no-tls-tickets
    ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

Note: Update ciphersuites as needed by your security policies.

Validate and reload HAProxy:

cd $agilesec_install_dir/services/haproxy
./haproxy -c -f haproxy.cfg
cd $agilesec_install_dir/scripts
sudo ./manage.sh restart haproxy

Note: HA proxy config validation must show no alerts.

Note: If using non-privileged port 8443, do not use sudo with ./manage.sh.

Verify TLS 1.3 is enforced and TLS 1.2 is rejected:

# Should succeed
openssl s_client -connect <agilesec-fqdn>:443 -tls1_3

# Should fail with handshake error
openssl s_client -connect <agilesec-fqdn>:443 -tls1_2

Result: The TLS 1.3 connection should complete successfully. The TLS 1.2 attempt should return an alert handshake failure, confirming the restriction is in place.

Note: If using non-privileged port 8443, add SNI to verification to avoid false failures in SNI/certificate-based deployments.

# Should succeed
openssl s_client -connect <agilesec-fqdn>:8443 -servername <agilesec-fqdn> -tls1_3

# Should fail with handshake error
openssl s_client -connect <agilesec-fqdn>:8443 -servername <agilesec-fqdn> -tls1_2