Outpost Advanced Security Configuration
This guide covers advanced security features for Arbitex Hybrid Outpost deployments that go beyond the baseline hardening covered in Outpost Security Hardening. These features provide network-layer access control, audit trail integrity, response security headers, and request validation. All settings are configured via environment variables and take effect without an outpost restart when applied through the admin config API.
1. IP Allowlist Enforcement
Section titled “1. IP Allowlist Enforcement”The IP allowlist restricts which client IP addresses can reach the outpost proxy endpoint. When enabled, only requests originating from configured CIDR ranges are allowed — all others receive a 403 Forbidden response. This provides a network-layer defense independent of authentication.
Configuration
Section titled “Configuration”| Variable | Type | Default | Description |
|---|---|---|---|
IP_ALLOWLIST_ENABLED |
bool | false |
Enable IP-based allowlisting. When true, only requests from IP_ALLOWLIST_CIDRS are accepted. |
IP_ALLOWLIST_CIDRS |
string | "" |
Comma-separated CIDR ranges (e.g. 10.0.0.0/8,192.168.1.0/24). |
IP_ALLOWLIST_ADMIN_EXEMPT |
bool | true |
When true, requests to /admin/* endpoints bypass the IP allowlist. |
How Client IP Is Determined
Section titled “How Client IP Is Determined”The middleware extracts the client IP using the following precedence:
X-Forwarded-Forheader — the first IP address in the header value is used. This is the standard behavior when the outpost sits behind a load balancer or reverse proxy.- Direct connection — if no
X-Forwarded-Forheader is present, the IP address from the TCP connection (scope["client"]) is used.
Denied Request Response
Section titled “Denied Request Response”When a request is denied by the IP allowlist, the outpost returns:
HTTP/1.1 403 ForbiddenContent-Type: application/json
{ "detail": "IP address not in allowlist", "client_ip": "192.168.1.xxx"}The client IP in the response is masked — the last octet of an IPv4 address (or the last group of an IPv6 address) is replaced with xxx. This prevents information leakage while still providing enough context for the client to understand the denial.
Each denied request increments an internal counter accessible via the admin API.
Admin API: IP Allowlist Status
Section titled “Admin API: IP Allowlist Status”GET /admin/api/ip-allowlist/statusReturns the current IP allowlist configuration and denial statistics:
{ "enabled": true, "cidrs": ["10.0.0.0/8", "192.168.1.0/24"], "admin_exempt": true, "denied_count": 42}| Field | Type | Description |
|---|---|---|
enabled |
bool | Whether IP allowlisting is currently active. |
cidrs |
list[string] | Configured CIDR ranges. |
admin_exempt |
bool | Whether admin endpoints bypass the allowlist. |
denied_count |
int | Total number of denied requests since the outpost started. |
Common Configurations
Section titled “Common Configurations”Internal-only deployment (RFC 1918 ranges):
IP_ALLOWLIST_ENABLED=trueIP_ALLOWLIST_CIDRS=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16IP_ALLOWLIST_ADMIN_EXEMPT=trueThis allows all private network addresses while blocking any traffic from public IP ranges. Suitable when the outpost is deployed on an internal network segment.
VPN-only access:
IP_ALLOWLIST_ENABLED=trueIP_ALLOWLIST_CIDRS=10.8.0.0/16IP_ALLOWLIST_ADMIN_EXEMPT=trueRestricts proxy access to the VPN subnet only. Adjust the CIDR to match your VPN address pool.
Specific application server IPs:
IP_ALLOWLIST_ENABLED=trueIP_ALLOWLIST_CIDRS=10.1.2.10/32,10.1.2.11/32,10.1.2.12/32IP_ALLOWLIST_ADMIN_EXEMPT=trueRestricts access to specific known application servers. Use /32 for individual IPs. This is the most restrictive configuration and is recommended when the set of clients is fixed and known.
Verifying IP Allowlist Configuration
Section titled “Verifying IP Allowlist Configuration”After configuring the IP allowlist, verify the configuration is active and correct:
# Check allowlist statuscurl -H "Authorization: Bearer $ADMIN_TOKEN" \ https://outpost:8301/admin/api/ip-allowlist/status
# Test from an allowed IP — should return 200curl -H "Authorization: Bearer $API_KEY" \ https://outpost:8300/v1/chat/completions \ -d '{"model":"gpt-4","messages":[{"role":"user","content":"test"}]}'
# Test from a non-allowed IP — should return 403# (run from a machine outside the configured CIDR ranges)curl -v https://outpost:8300/v1/chat/completionsMonitor the denied_count field in the status response to track how many requests have been denied since the outpost started. A steadily increasing denied count with no corresponding user complaints typically indicates automated scanning or misconfigured clients.
Hot Reload
Section titled “Hot Reload”IP allowlist settings are hot-reloadable. After updating the environment variables, apply the new configuration without restarting the outpost:
POST /admin/api/config/applyThe middleware reads configuration at request time from the settings object, so changes take effect on the next request after config/apply completes.
2. Request Body Hash Logging
Section titled “2. Request Body Hash Logging”Request body hash logging provides an audit trail integrity mechanism by computing an HMAC hash of the original request (and optionally response) body before any DLP processing occurs. This creates a cryptographic chain-of-custody record — auditors can verify that the body recorded in the audit trail matches the original content sent by the client.
Configuration
Section titled “Configuration”| Variable | Type | Default | Description |
|---|---|---|---|
BODY_HASH_LOGGING_ENABLED |
bool | false |
Enable HMAC hashing of request/response bodies for audit trail integrity. |
BODY_HASH_ALGORITHM |
string | sha256 |
Hash algorithm: sha256 or sha512. |
BODY_HASH_LOG_RESPONSE |
bool | false |
When true, also hash response bodies in audit events. |
Purpose and Chain-of-Custody
Section titled “Purpose and Chain-of-Custody”Body hash logging solves a specific compliance problem: proving that the content recorded in audit events matches the original content that was submitted. When DLP scans redact or modify content, the pre-scan body hash provides proof of the original submission.
The hash is computed as an HMAC (keyed hash), not a plain hash. This prevents an attacker with write access to audit logs from recomputing hashes for modified content — without the HMAC key, forged hashes are detectable.
HMAC Key Source
Section titled “HMAC Key Source”The HMAC key is selected using the following precedence:
AUDIT_HMAC_KEY— the dedicated audit HMAC key (preferred).ADMIN_JWT_SECRET— falls back to the admin JWT secret if no dedicated HMAC key is set.
Audit Event Fields
Section titled “Audit Event Fields”When body hash logging is enabled, the following fields are added to each DLP scan audit event:
| Field | Present When | Description |
|---|---|---|
request_body_hash |
Always (when enabled) | HMAC hash of the original request body before DLP processing. |
response_body_hash |
BODY_HASH_LOG_RESPONSE=true |
HMAC hash of the response body from the upstream provider. |
Hash values are hex-encoded strings. SHA-256 produces 64 characters; SHA-512 produces 128 characters.
Admin API: Body Hash Configuration
Section titled “Admin API: Body Hash Configuration”GET /admin/api/body-hash/configReturns the current body hash logging configuration:
{ "enabled": true, "algorithm": "sha256", "log_response": false, "key_source": "AUDIT_HMAC_KEY"}| Field | Type | Description |
|---|---|---|
enabled |
bool | Whether body hash logging is active. |
algorithm |
string | Hash algorithm in use (sha256 or sha512). |
log_response |
bool | Whether response bodies are also hashed. |
key_source |
string | Which key is being used: AUDIT_HMAC_KEY or ADMIN_JWT_SECRET. |
Verification Workflow
Section titled “Verification Workflow”To verify that body hash logging is working correctly:
- Enable body hash logging in the outpost configuration.
- Send a test request through the outpost proxy.
- Retrieve the audit event from
GET /admin/audit/recent. - Verify the hash field — the
request_body_hashfield should be present in the audit event. - Compute the expected hash — using the same HMAC key and algorithm, compute the hash of the original request body and compare it to the recorded hash.
# Compute expected SHA-256 HMAC for verificationecho -n '{"model":"gpt-4","messages":[{"role":"user","content":"test"}]}' | \ openssl dgst -sha256 -hmac "$AUDIT_HMAC_KEY" -hexIf the computed hash matches the request_body_hash in the audit event, the chain-of-custody is intact — the recorded body hash corresponds to the original request content.
Privacy Note
Section titled “Privacy Note”Body hashes are one-way HMAC digests. The original body content cannot be recovered from the hash. Body content itself is never written to audit logs — only the cryptographic fingerprint is recorded. This satisfies audit trail requirements without introducing additional data exposure risk.
3. Security Headers Reference
Section titled “3. Security Headers Reference”The outpost applies a fixed set of security headers to every HTTP response via the SecurityHeadersMiddleware. These headers are not configurable — they are always applied and cannot be disabled.
Response Headers
Section titled “Response Headers”| Header | Value | Purpose |
|---|---|---|
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Forces HTTPS for 1 year. Browsers that have visited the outpost will refuse plaintext HTTP connections. |
X-Content-Type-Options |
nosniff |
Prevents browsers from MIME-sniffing the response body. The declared Content-Type is authoritative. |
X-Frame-Options |
DENY |
Prevents the outpost response from being embedded in an iframe. Mitigates clickjacking. |
Content-Security-Policy |
default-src 'none' |
Blocks all resource loading. The outpost serves JSON API responses only — no scripts, styles, or images should be loaded. |
Referrer-Policy |
no-referrer |
Suppresses the Referer header on outgoing requests from any page that loaded an outpost response. |
These values are derived from the outpost source code (outpost/security_headers.py). They are applied at the ASGI middleware layer and cannot be overridden by route handlers.
X-Request-ID
Section titled “X-Request-ID”In addition to the five security headers above, the outpost generates a unique X-Request-ID header on every response:
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000Characteristics:
- Generated per request as a UUID v4. Each request receives a unique identifier.
- Stored in scope state (
scope["state"]["request_id"]) so downstream handlers and middleware can access it. - Emitted as a response header on every HTTP response.
- Propagated to audit events — the
request_idfield in DLP scan audit events matches theX-Request-IDheader. - Included in structured JSON logs as
request_id— enables log correlation across middleware, route handlers, and audit events.
Use the X-Request-ID value to trace a single request through outpost logs, audit events, and SIEM exports.
4. Request Validation
Section titled “4. Request Validation”The outpost validates incoming requests at two layers: a middleware layer that checks Content-Length headers, and a route-level layer that reads actual body bytes.
Middleware Layer: Content-Length Validation
Section titled “Middleware Layer: Content-Length Validation”| Variable | Type | Default | Description |
|---|---|---|---|
REQUEST_MAX_BODY_BYTES |
int | 1048576 (1 MB) |
Maximum allowed Content-Length for any request. Requests exceeding this limit are rejected before the body is read. |
When a request’s Content-Length header exceeds the configured limit, the middleware returns:
HTTP/1.1 413 Request Entity Too LargeContent-Type: application/json
{ "detail": "Request body too large"}This is a fast-path rejection — the body is not read from the socket, saving bandwidth and CPU.
Content-Type Enforcement
Section titled “Content-Type Enforcement”The middleware enforces Content-Type: application/json on all POST, PUT, and PATCH requests. Requests with a missing or non-JSON content type receive a 415 Unsupported Media Type response.
Exempt paths: The following health and readiness endpoints are exempt from content-type enforcement:
/health/ready/live/metrics
These endpoints may receive requests without a Content-Type header (e.g. from Kubernetes liveness probes).
Route-Level: Actual Body Size Enforcement
Section titled “Route-Level: Actual Body Size Enforcement”The /v1/chat/completions endpoint performs a second body size check by reading the actual body bytes. This catches cases where the Content-Length header is absent, spoofed, or the transfer uses chunked encoding.
| Variable | Type | Default | Description |
|---|---|---|---|
MAX_REQUEST_BODY_MB |
int | 10 |
Maximum allowed actual body size in megabytes for /v1/chat/completions. |
The route-level check reads the full body into memory and compares the byte count against the configured limit. If the body exceeds the limit:
HTTP/1.1 413 Request Entity Too LargeContent-Type: application/json
{ "error": { "message": "Request body exceeds 10MB limit", "type": "request_too_large" }}Middleware Registration Order
Section titled “Middleware Registration Order”The outpost registers middleware in the following order (outermost to innermost):
- SecurityHeadersMiddleware — adds security headers and
X-Request-IDto all responses. - IPAllowlistMiddleware — blocks requests from non-allowed IPs (when enabled).
- RequestValidationMiddleware — enforces body size and content-type rules.
A request passes through these layers in order. A denied IP never reaches the request validation layer. A request that passes IP validation but exceeds body size limits is rejected before reaching route handlers.
5. Combined Security Checklist
Section titled “5. Combined Security Checklist”This checklist combines all items from the pre-deployment checklist in the base hardening guide with the advanced security features documented in this guide. Complete all items before placing the outpost in production.
TLS and Certificates
Section titled “TLS and Certificates”- TLS certificate paths are valid. Confirm
TLS_SERVER_CERT_PATHandTLS_SERVER_KEY_PATHexist and are readable by the outpost process. Verify the certificate chain:openssl verify -CAfile <ca.pem> <server.pem>. - Certificate expiry is at least 30 days from deployment. Check
cert_expiry_daysviaGET /admin/api/sync-statusafter startup. Schedule rotation before the renewal threshold. - mTLS client CA is configured if
TLS_VERIFY_CLIENT=true. ConfirmTLS_CA_CERT_PATHcontains the correct CA certificate and test with a client certificate before enabling.
Authentication and Secrets
Section titled “Authentication and Secrets”-
OUTPOST_EMERGENCY_ADMIN_KEYis set and non-empty. Generate withopenssl rand -hex 32. An empty key disables the emergency fallback. -
AUDIT_HMAC_KEYis set. The outpost will not start without this key. Verify it is populated in the secrets store. -
POLICY_HMAC_KEYis set. Required for policy bundle integrity verification. Do not setINSECURE_SKIP_HMAC=truein production. - RS256 JWKS URL or RSA public key is configured if OAuth proxy authentication is required. Confirm
OAUTH_JWT_PUBLIC_KEYorOAUTH_JWKS_URLis set and the endpoint is reachable.
Rate Limiting and Request Validation
Section titled “Rate Limiting and Request Validation”- Rate limits are appropriate for expected traffic. Default limits (60 rpm proxy, 30 rpm admin) are conservative. Adjust
RATE_LIMIT_REQUESTS_PER_MINUTEandRATE_LIMIT_BURSTfor your workload. -
REQUEST_MAX_BODY_BYTESis set appropriately. Default 1 MB. Increase if your application sends large prompts. Do not disable. -
MAX_REQUEST_BODY_MBis set appropriately. Default 10 MB. This is the hard limit on actual body bytes for/v1/chat/completions.
IP Allowlist
Section titled “IP Allowlist”- IP allowlist is enabled if the outpost should only accept traffic from known sources. Set
IP_ALLOWLIST_ENABLED=trueand configureIP_ALLOWLIST_CIDRS. - CIDR ranges are minimal. Only include the specific subnets or IPs that need access. Avoid overly broad ranges (e.g.
0.0.0.0/0). - Admin exemption is intentional.
IP_ALLOWLIST_ADMIN_EXEMPT=true(default) allows admin access from any IP. If your admin port is network-accessible, consider setting this tofalseand including admin IPs in the CIDR list. - Reverse proxy X-Forwarded-For is configured correctly. The outpost trusts the first IP in
X-Forwarded-For. Ensure your proxy sets (not appends to) this header.
Audit Trail Integrity
Section titled “Audit Trail Integrity”- Audit logging path is writable. The SQLite audit database must be on a persistent volume. Ephemeral storage causes audit loss on pod restart.
- Body hash logging is enabled if chain-of-custody is required. Set
BODY_HASH_LOGGING_ENABLED=trueand configureBODY_HASH_ALGORITHM. - Dedicated
AUDIT_HMAC_KEYis set for body hash logging. Do not rely on theADMIN_JWT_SECRETfallback in production. - SIEM export is configured if required by compliance policy. Configure
SIEM_DIRECT_URLandSIEM_DIRECT_TOKEN. Verify connectivity from the outpost network segment.
Operational
Section titled “Operational”-
LOG_LEVELis set toinfo. Debug logging may expose request headers and policy evaluation details. - Air-gap mode is configured if the outpost cannot reach
PLATFORM_MANAGEMENT_URL. SetOUTPOST_AIRGAP=trueand configureAIRGAP_POLICY_PATH.
See Also
Section titled “See Also”- Outpost Security Hardening — baseline security configuration: TLS, JWT validation, rate limiting, admin access control, secret management
- Security Hardening — Platform — platform-side security hardening
- Outpost Health Monitoring — heartbeat architecture, cert expiry monitoring, Prometheus metrics
- Audit Log Verification — HMAC chain integrity verification for audit events
- Outpost Administration — budget enforcement, CredInt, health monitoring, JWT validation, PVC recovery, security hardening, SIEM direct integration