Skip to main content

TLS & Network Hardening

These controls apply to the HTTP transports (sse and streamable-http) and govern who can reach the server over the network.

TLS

HTTPS is required by default for non-localhost deployments. The server refuses to start on a non-localhost interface without TLS certificates unless ZSCALER_MCP_ALLOW_HTTP=true is set.

ZSCALER_MCP_TLS_CERTFILE=/path/to/cert.pem
ZSCALER_MCP_TLS_KEYFILE=/path/to/key.pem

# Optional
ZSCALER_MCP_TLS_KEYFILE_PASSWORD=your-key-password
ZSCALER_MCP_TLS_CA_CERTS=/path/to/ca-bundle.pem

Generate a self-signed cert for local testing:

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=localhost"

For managed hosts (Cloud Run, Azure Container Apps, ALB-fronted ECS) where TLS terminates at the platform edge, set ZSCALER_MCP_ALLOW_HTTP=true — the platform's TLS protects the wire.

Host header validation

ZSCALER_MCP_ALLOWED_HOSTS=mcp.example.com,*.example.com

Requests whose Host header doesn't match the allowlist receive 421 Misdirected Request. Supports wildcards. Disabled by default for stdio; enabled by default for HTTP.

To disable (for debugging only):

ZSCALER_MCP_DISABLE_HOST_VALIDATION=true

Source IP ACL

Restrict which client IPs can connect:

ZSCALER_MCP_ALLOWED_SOURCE_IPS=10.0.0.0/8,172.16.0.5,2001:db8::/32

When unset, source IP filtering is disabled and deferred to upstream controls (security groups, firewall rules).

Health-check endpoints (/health, /healthz, /ready) are always exempt so load-balancer probes work. Disallowed IPs receive 403 Forbidden.

.env plaintext-secret scanner

When starting with HTTP transports, the server automatically scans any .env file in the working directory for plaintext secrets (values containing SECRET, PASSWORD, KEY, or TOKEN). If detected, a security warning is logged recommending a secrets manager or environment variables.

Output sanitization

Every string in every tool result passes through a three-stage sanitizer before reaching the agent:

  1. Invisible / control characters stripped — zero-width characters, BiDi controls (LRO, RLO, etc.), Arabic letter mark, soft hyphen, BOM. NBSP normalized to space. Tab/LF/CR preserved.
  2. HTML / Markdown sanitizedbleach removes every HTML tag and comment; Markdown image syntax collapses to alt text; Markdown links keep text + (visible) URL but lose the directive.
  3. Code-fence info-strings filtered — fences with role-impersonation tokens (system, assistant, tool, ignore, …) get neutralized to text. Code bodies preserved.

This defends against prompt-injection payloads embedded in admin-editable Zscaler resources (rule descriptions, location names, label descriptions, etc.).

On by default. Opt out (diagnostics only) with:

ZSCALER_MCP_DISABLE_OUTPUT_SANITIZATION=true

Security posture banner

On startup, the server logs a consolidated Security Posture Banner summarizing the active configuration — transport, host validation, auth mode, TLS status, write mode, allowlist, entitlement filter result. This makes it easy to verify the state at a glance.

Audit logging

ZSCALER_MCP_LOG_TOOL_CALLS=true

Every tool invocation logs:

[TOOL CALL] zia_list_locations | args: {page: 1, page_size: 50, name: "HQ"}
[TOOL OK] zia_list_locations | 342ms | 15 items

Sensitive parameters (any name containing password, secret, token, key, credential) are auto-redacted. Full response data is never logged — only a summary.

Defense-in-depth summary

LayerScopeOn by default?
Read-only modeevery transport
Write allowlistevery transport✅ (allowlist mandatory)
HMAC delete confirmationsevery transport
OneAPI entitlement filterevery transport
Toolset selectionevery transportoptional
Output sanitizationevery transport
TLSHTTP only✅ (non-localhost)
Host validationHTTP only
Source IP ACLHTTP onlyoptional
MCP client authHTTP onlyauto when configured
.env secret scannerHTTP only
Audit loggingevery transportoptional