Configuring CORS and HTTP Security Headers
This guide walks through common CORS and HTTP security header configurations for different deployment scenarios.
Background
When a browser-based application (SPA, mobile web, etc.) makes requests to Bondy from a different origin, the browser enforces the CORS policy. Without the right CORS headers, the browser blocks the response.
Bondy configures CORS and security headers at the listener level in bondy.conf. Every endpoint served by that listener (API Gateway routes, SSE/long-poll WAMP transports, OIDC login/callback, OAuth2 token endpoints) inherits the same CORS policy.
INFO
See the HTTP Security Headers Reference for the complete list of configuration keys.
Scenario 1: Development (allow everything)
During development you typically want browsers to allow all cross-origin requests without friction. This is the default configuration:
api_gateway.http.cors.enabled = on
api_gateway.http.cors.allowed_origins = *WARNING
Wildcard origin forces Access-Control-Allow-Credentials to false. If your frontend needs to send cookies or Authorization headers, switch to an explicit allowlist or auto mode.
Scenario 2: Single frontend origin
The most common production setup — one SPA served from a known domain:
api_gateway.https.cors.allowed_origins = https://app.example.comWith this configuration:
- Requests with
Origin: https://app.example.comreceiveAccess-Control-Allow-Origin: https://app.example.comandAccess-Control-Allow-Credentials: true - Requests from any other origin (or no origin) receive no CORS headers, causing the browser to block them
- A
Vary: Originheader is added so caches distinguish between origins
Scenario 3: Multiple frontend origins
When several frontends share the same Bondy cluster:
api_gateway.https.cors.allowed_origins = https://app.example.com, https://admin.example.com, https://partner.example.comThe incoming Origin header is matched against this list. Only exact matches are reflected back.
Scenario 4: Wildcard subdomains
When you have many subdomains under the same base domain (e.g. per-tenant subdomains), use the *. prefix instead of listing every one:
api_gateway.https.cors.allowed_origins = *.example.comThis matches https://app.example.com, https://tenant1.example.com, etc. It does not match the bare domain https://example.com — add it explicitly if needed:
api_gateway.https.cors.allowed_origins = *.example.com, https://example.comYou can mix wildcard patterns with exact origins:
api_gateway.https.cors.allowed_origins = *.example.com, https://partner.other.comThe exact requesting origin is always reflected back in the Access-Control-Allow-Origin header. The *. pattern is purely a server-side config convenience.
Scenario 5: Same-origin (auto mode)
If your frontend is served from the same domain as Bondy (e.g. via a reverse proxy), you can derive the origin automatically from the request:
api_gateway.https.cors.allowed_origins = autoBondy constructs the origin from Scheme://Host[:Port] of the incoming request (default ports 80 and 443 are omitted). This is equivalent to "same-origin only".
Scenario 6: Per-endpoint overrides via API Gateway specs
When different API endpoints need different CORS policies, you can define CORS headers directly in the API Gateway specification using MOPS expressions:
{
"variables": {
"partner_cors": {
"access-control-allow-origin": "https://partner.example.com",
"access-control-allow-credentials": "true",
"access-control-allow-methods": "GET,POST",
"access-control-allow-headers": "content-type,authorization",
"access-control-max-age": "3600"
}
},
"versions": {
"1.0.0": {
"paths": {
"/partner/data": {
"get": {
"response": {
"on_result": {
"headers": "{{variables.partner_cors}}",
"body": "{{action.result}}"
}
}
}
}
}
}
}
}When an API spec defines an access-control-allow-origin header, it takes precedence over the listener-level configuration. Endpoints without spec-defined CORS headers fall back to the listener configuration.
Hardening security headers
Beyond CORS, Bondy can set standard security response headers on every response. These are configured per-listener and address common findings from security scanners.
Recommended production settings
## Enforce HTTPS via HSTS (HTTPS listener only)
api_gateway.https.security_headers.hsts = max-age=31536000; includeSubDomains
## Prevent clickjacking
api_gateway.https.security_headers.frame_options = DENY
## Prevent MIME sniffing
api_gateway.https.security_headers.content_type_options = nosniff
## Content Security Policy (tune to your application)
api_gateway.https.security_headers.content_security_policy = default-src 'self'; frame-ancestors 'none'
## Suppress server version disclosure
api_gateway.https.server_header =Disabling individual headers
Set any header value to an empty string to suppress it:
## No CSP (too complex for this deployment)
api_gateway.https.security_headers.content_security_policy =
## No HSTS (TLS terminated at load balancer)
api_gateway.https.security_headers.hsts =Disabling all security headers
api_gateway.http.security_headers.enabled = offVerifying your configuration
After configuring, verify with curl:
# Check CORS headers on a preflight request
curl -s -D - -o /dev/null \
-X OPTIONS \
-H "Origin: https://app.example.com" \
https://your-bondy-host:18080/wamp/sse/open
# Check security headers on a regular request
curl -s -D - -o /dev/null \
https://your-bondy-host:18081/pingLook for:
access-control-allow-originmatching your frontend origin (not*)access-control-allow-credentials: true(when using an allowlist)x-frame-options: DENYorSAMEORIGINx-content-type-options: nosniffstrict-transport-security(on HTTPS only)serverheader absent or generic
