Skip to content

Diagnostics & Verification

This page provides a layered UAT verification matrix for validating your Client-Side Defense deployment end-to-end. Each test case follows the infrastructure dependency chain — from DNS resolution through CSD telemetry — so you can systematically prove every component is working correctly.

These commands are the API equivalent of the CSD Console Walkthrough — use them when you need to verify from the terminal, automate monitoring, or demonstrate CSD capabilities without the UI.

Set up your environment variables as described in API Automation — Environment Setup:

Terminal window
set -a && source .env && set +a

All commands below use the xTOKENx placeholder format. Substitute with your environment variables ($F5XC_API_TOKEN, $F5XC_NAMESPACE, etc.) or use the interactive form at the top of the page.

Many CSD endpoints require epoch timestamps (seconds since Unix epoch). These one-liners compute start/end times for common ranges.

Cross-platform (macOS + Linux):

Terminal window
# Current time as epoch seconds
NOW=$(date +%s)
# 1 hour ago
START_1H=$(( NOW - 3600 ))
# 24 hours ago
START_24H=$(( NOW - 86400 ))
# 7 days ago
START_7D=$(( NOW - 604800 ))
# 30 days ago
START_30D=$(( NOW - 2592000 ))
PresetSecondsShell expression
1 hour3,600$(( $(date +%s) - 3600 ))
24 hours86,400$(( $(date +%s) - 86400 ))
7 days604,800$(( $(date +%s) - 604800 ))
30 days2,592,000$(( $(date +%s) - 2592000 ))

Each test below follows this structure:

FieldDescription
Test IDLayer number + sequential ID (e.g., DNS-1, TLS-2)
What it provesThe specific infrastructure fact being verified
CommandReady-to-run curl or dig command
PASS / FAILExpected output for healthy vs. unhealthy state
FixLink to the relevant setup or troubleshooting section

DNS is the foundation — if the domain does not resolve to the load balancer VIP, nothing else works.

What it proves: The domain resolves to the load balancer VIP IP address.

Terminal window
dig +short xF5XC_DOMAINNAMEx A
ResultMeaning
PASS — VIP IP returned (e.g., 72.19.3.185)Domain resolves to the LB virtual IP
FAIL — empty responseDNS A record not configured

Fix: API Automation — Step 4: Configure DNS

What it proves: The ACME challenge record exists for automatic TLS certificate provisioning.

Terminal window
dig +short _acme-challenge.xF5XC_DOMAINNAMEx CNAME
dig +short _acme-challenge.xF5XC_DOMAINNAMEx TXT
ResultMeaning
PASS — CNAME to *.autocerts.ves.volterra.io. (external DNS)ACME challenge CNAME is in place
PASS — TXT record with domain value (F5 Distributed Cloud (F5 XC) managed DNS)Platform-managed ACME challenge via TXT record
FAIL — both emptyACME record not configured; certificate will stay in PreDomainChallengePending

Fix: For external DNS, create CNAME: _acme-challenge.xF5XC_DOMAINNAMEx*.autocerts.ves.volterra.io. For F5 XC managed DNS, enable allow_http_lb_managed_records on the DNS zone — see DNS-4. See API Automation — Step 4.

What it proves: Whether F5 XC is the authoritative DNS provider for the root domain.

Terminal window
dig +short NS xF5XC_ROOT_DOMAINx
ResultMeaning
Includes ns1.f5clouddns.com and ns2.f5clouddns.comF5 XC managed DNS — records can be auto-created
Other nameserversExternal DNS — records must be created manually

Fix: API Automation — Detect DNS Authority

What it proves: The F5 XC DNS zone allows automatic record creation for load balancers (F5 XC managed DNS only).

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx" \
| jq '.spec.primary.allow_http_lb_managed_records'
ResultMeaning
PASStruePlatform will auto-create A and ACME records for LBs
FAILfalse or nullManaged records disabled; enable via zone update

Fix: API Automation — Option A: F5 XC Managed DNS


After DNS resolves, the load balancer must have a valid TLS certificate.

What it proves: The automatic TLS certificate has been issued on the HTTPS LB.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https" \
| jq '.spec.cert_state'
ResultMeaning
PASS"CertificateValid" or "AutoCertRenewing"Certificate is valid and active
WARN"DomainChallengePending" or "DomainChallengeStarted"ACME challenge in progress — wait 5–10 minutes
INFO"AutoCertDomainRateLimited"Let’s Encrypt rate limit hit — expected in demo environments, does not affect HTTP LB
FAIL"PreDomainChallengePending"ACME challenge record missing — see DNS-2

Fix: Ensure the ACME challenge record is in place (CNAME for external DNS, or enable managed records for F5 XC DNS). Certificate provisioning takes 5–10 minutes after the record is configured. If stuck beyond 15 minutes, see Certificate Stuck — Clean Recreation. If rate-limited, the HTTP LB is unaffected.

What it proves: The certificate covers the expected domain and shows expiry information. Only applicable to the HTTPS LB.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https" \
| jq '{
cert_state: .spec.cert_state,
auto_cert_info: .spec.auto_cert_info
}'
ResultMeaning
PASSauto_cert_info contains dns_records and certificate metadataCertificate provisioning details available
FAILauto_cert_info is null or emptyCertificate not yet provisioned

What it proves: The TLS certificate is valid and the handshake completes from the client side. Only applicable when the HTTPS LB has a valid certificate.

Terminal window
curl -sv "https://xF5XC_DOMAINNAMEx/" 2>&1 \
| grep -E 'SSL connection|subject:|expire date:|issuer:'
ResultMeaning
PASS — shows SSL connection using TLSv1.3, valid subject and expiryEnd-to-end TLS is working
FAIL — connection refused or certificate errorCheck DNS resolution and cert state; if rate-limited, use HTTP instead

What it proves: The HTTPS load balancer reports the correct ACME target for certificate validation.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https" \
| jq '{
vip_ip: .spec.dns_info[0].ip_address,
acme_target: .spec.auto_cert_info.dns_records
}'
ResultMeaning
PASSvip_ip and acme_target populatedDNS records can be verified against these values
FAILnull valuesLB may not have https_auto_cert configured

The load balancer must be in a ready state and correctly configured.

What it proves: The HTTP load balancer (primary) is fully operational and accepting traffic.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \
| jq '.spec.state'
ResultMeaning
PASS"VIRTUAL_HOST_READY"LB is operational
FAIL"VIRTUAL_HOST_PENDING_A_RECORD"DNS A record not configured — see DNS-1

Fix: LB Stuck in VIRTUAL_HOST_PENDING_A_RECORD

What it proves: The HTTP load balancer is configured for the correct domain(s).

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \
| jq '.spec.domains'
ResultMeaning
PASS — array contains your FQDN (e.g., ["app.example.com"])LB is configured for the expected domain
FAIL — missing or wrong domainUpdate the LB spec with the correct domain

What it proves: Client-Side Defense is enabled on the load balancer with the correct JS injection policy.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \
| jq '{
csd_enabled: (if .spec.client_side_defense then true else false end),
js_policy: .spec.client_side_defense.policy
}'
ResultMeaning
PASScsd_enabled: true with js_insert_all_pages in policyCSD active with JS injection on all pages
FAILcsd_enabled: falseCSD not configured on the LB

Fix: API Reference — Enable CSD on a Load Balancer

What it proves: The load balancer references the correct origin pool with expected weight and priority.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \
| jq '.spec.default_route_pools[] | {
pool: .pool.name,
namespace: .pool.namespace,
weight: .weight,
priority: .priority
}'
ResultMeaning
PASS — shows your origin pool name with weight/priorityLB is routing to the correct backend
FAIL — empty or wrong poolUpdate the LB default_route_pools configuration

LB-5: Deployment Status (Per-Site Conditions)

Section titled “LB-5: Deployment Status (Per-Site Conditions)”

What it proves: The load balancer is deployed and reports healthy conditions across all sites.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \
| jq '{
state: .spec.state,
dns_info: .spec.dns_info,
host_name: .spec.host_name
}'
ResultMeaning
PASSstate is VIRTUAL_HOST_READY, dns_info populatedLB fully deployed with VIP assigned
FAIL — pending state or empty dns_infoDNS or certificate issue blocking deployment

What it proves: Comprehensive snapshot of both LB configurations for debugging.

HTTP LB (primary):

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \
| jq '{
name: .metadata.name,
state: .spec.state,
domains: .spec.domains,
csd_enabled: (if .spec.client_side_defense then true else false end),
route_pools: [.spec.default_route_pools[] | .pool.name],
advertise: (if .spec.advertise_on_public_default_vip then "public_default_vip" else "custom" end)
}'

HTTPS LB (secondary — includes cert state):

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https" \
| jq '{
name: .metadata.name,
state: .spec.state,
cert_state: .spec.cert_state,
domains: .spec.domains,
csd_enabled: (if .spec.client_side_defense then true else false end),
route_pools: [.spec.default_route_pools[] | .pool.name]
}'

The origin pool defines the backend servers that the load balancer routes traffic to.

What it proves: The origin pool exists with the correct backend server, port, and LB algorithm.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/xF5XC_ORIGIN_POOLx" \
| jq '{
name: .metadata.name,
origin_servers: [.spec.origin_servers[] | {
ip: .public_ip.ip,
labels: .labels
}],
port: .spec.port,
lb_algorithm: .spec.loadbalancer_algorithm,
endpoint_selection: .spec.endpoint_selection
}'
ResultMeaning
PASS — shows correct IP, port, and algorithmOrigin pool configured correctly
FAIL404 or wrong valuesOrigin pool missing or misconfigured

Fix: API Automation — Step 2: Create Origin Pool

What it proves: Whether TLS is configured for the connection between the LB and origin server.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/xF5XC_ORIGIN_POOLx" \
| jq '{
tls_config: (if .spec.no_tls then "no_tls (plaintext)" elif .spec.use_tls then "use_tls (encrypted)" else "unknown" end)
}'
ResultMeaning
no_tls (plaintext)LB connects to origin over HTTP (expected for Juice Shop demo)
use_tls (encrypted)LB connects to origin over HTTPS

What it proves: The origin pool has the correct healthcheck linked (if applicable).

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/xF5XC_ORIGIN_POOLx" \
| jq '.spec.healthcheck'
ResultMeaning
PASS — array with healthcheck reference (name, namespace, kind)Healthcheck is linked
OK — empty array []No healthcheck (acceptable — CSD does not require one)
FAIL — expected a healthcheck but array is emptyHealthcheck was not found at creation time — see Healthcheck Not Linked

What it proves: The origin server is reachable from the client side (does not test LB-to-origin path).

Terminal window
curl -s -o /dev/null -w '%{http_code}' \
"http://xF5XC_ORIGIN_IPx:xF5XC_ORIGIN_PORTx/"
ResultMeaning
PASS200 (or other valid HTTP code)Origin server is responding
FAIL000 or connection refusedOrigin server unreachable from this network

Health checks monitor backend availability. They are optional for CSD but useful for production deployments.

What it proves: The healthcheck exists with the correct type, path, timing, and thresholds.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks/xF5XC_HC_NAMEx" \
| jq '{
name: .metadata.name,
type: (if .spec.http_health_check then "HTTP" elif .spec.tcp_health_check then "TCP" else "unknown" end),
path: .spec.http_health_check.path,
expected_status: .spec.http_health_check.expected_status_codes,
timeout: .spec.timeout,
interval: .spec.interval,
unhealthy_threshold: .spec.unhealthy_threshold,
healthy_threshold: .spec.healthy_threshold
}'
ResultMeaning
PASS — shows HTTP type with path / and expected 200Healthcheck configured correctly
FAIL404 responseHealthcheck does not exist (may have been skipped — see Phase 1 Step 1)

What it proves: Enumerates all healthchecks in the namespace to verify naming and count.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks" \
| jq -r '
["NAME", "NAMESPACE", "DESCRIPTION"],
(.items[] | [
.name,
.namespace,
(.description | if length == 0 then "—" else . end)
])
| @tsv' | column -t

CSD must be enabled at the tenant level and have the JavaScript injection tag configured.

What it proves: CSD is configured and enabled for the tenant.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/status" \
| jq '{isConfigured, isEnabled}'
ResultMeaning
PASS — both trueCSD is active for this tenant
FAILisConfigured: falseCSD not enabled at tenant level — contact F5 XC administrator
FAILisEnabled: falseCSD configured but not active

What it proves: The CSD JavaScript injection tag is generated and ready for injection.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/js_configuration" \
| jq '{has_script_tag: (.scriptTag | length > 0)}'
ResultMeaning
PASShas_script_tag: trueJS injection tag is configured
FAILhas_script_tag: falseNo script tag — verify CSD is enabled and a protected domain is registered

What it proves: Shows the full script tag for verification or manual injection.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/js_configuration" \
| jq '.scriptTag'

What it proves: The root domain is registered as a CSD protected domain on the tenant.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains" \
| jq '.items[] | {name, namespace, description}'
ResultMeaning
PASS — shows an item with a non-empty nameDomain is protected
FAIL — items have empty name and namespace fieldsNo protected domains registered — see Phase 1 Step 6

What it proves: The CSD JavaScript is actually being injected into page responses served by the load balancer.

Terminal window
curl -s "http://xF5XC_DOMAINNAMEx/" \
| grep -oE '(zeronaught|shape)\.com[^"]*' | head -1
ResultMeaning
PASS — returns a zeronaught.com or shape.com URL fragment (e.g., zeronaught.com/__imp_apg__/js/...)CSD JavaScript is being injected into pages
FAIL — empty outputJS not injected — check LB-3 and CSD-1

Verify that live traffic is reaching the load balancer and being processed correctly.

What it proves: Traffic is reaching the load balancer. Zero results means no traffic is arriving.

Terminal window
curl -s -X POST \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
-H "Content-Type: application/json" \
-d '{
"start_time": "'"$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-24H +%Y-%m-%dT%H:%M:%SZ)"'",
"end_time": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"
}' \
"xF5XC_API_URLx/api/data/namespaces/xF5XC_NAMESPACEx/access_logs/aggregation" \
| jq '{total_requests: .total_hits}'
ResultMeaning
PASStotal_requests is a non-zero string (e.g., "380")Traffic is flowing through the LB
FAIL"0" or no dataNo traffic reaching the LB — check DNS-1 and LB-1

What it proves: The breakdown of response status codes reveals error patterns.

Terminal window
curl -s -X POST \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
-H "Content-Type: application/json" \
-d '{
"start_time": "'"$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-24H +%Y-%m-%dT%H:%M:%SZ)"'",
"end_time": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"sort": "DESCENDING",
"limit": 100
}' \
"xF5XC_API_URLx/api/data/namespaces/xF5XC_NAMESPACEx/access_logs" \
| jq -r '
[.logs[] | fromjson | .rsp_code_class]
| group_by(.) | map({class: .[0], count: length})
| sort_by(-.count)
| ["STATUS_CLASS", "COUNT"], (.[] | [.class, .count])
| @tsv' | column -t

Expected output for a healthy site:

STATUS_CLASS COUNT
2xx 82
downstream_remote_disconnect 18
ResultMeaning
PASS — majority 2xxSite is serving successful responses
WARN — high 4xx countClient errors (bad URLs, missing resources)
FAIL — high 5xx countServer errors — check origin server health

The downstream_remote_disconnect class indicates the client closed the connection before the response completed (common for long-polling or WebSocket upgrade requests).

What it proves: Individual requests are being logged with correct fields.

Terminal window
curl -s -X POST \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
-H "Content-Type: application/json" \
-d '{
"start_time": "'"$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)"'",
"end_time": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"sort": "DESCENDING",
"limit": 10
}' \
"xF5XC_API_URLx/api/data/namespaces/xF5XC_NAMESPACEx/access_logs" \
| jq -r '
["TIMESTAMP", "METHOD", "PATH", "STATUS", "SRC_IP"],
(.logs[] | fromjson | [.["@timestamp"], .method, .req_path, .rsp_code, .src_ip])
| @tsv' | column -t

What it proves: Access logs confirm CSD JavaScript is being injected into responses.

Terminal window
curl -s -X POST \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
-H "Content-Type: application/json" \
-d '{
"start_time": "'"$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)"'",
"end_time": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"sort": "DESCENDING",
"limit": 20
}' \
"xF5XC_API_URLx/api/data/namespaces/xF5XC_NAMESPACEx/access_logs" \
| jq '[.logs[] | fromjson | select(.csd_js_injection == "true")] | length as $injected |
{injected_count: $injected, total_sampled: 20}'
ResultMeaning
PASSinjected_count > 0CSD JS is being injected into page responses
FAILinjected_count: 0JS not injected — check CSD-1 and LB-3

What it proves: A complete request flows from client through DNS, LB, and origin and returns a valid response.

Terminal window
curl -sv "http://xF5XC_DOMAINNAMEx/" 2>&1 \
| grep -E 'Connected to|< HTTP|< content-type'
ResultMeaning
PASS — shows connection, HTTP 200, and content-typeFull stack is operational
FAIL — connection refused or DNS errorStart debugging at Layer 1: DNS

These commands query the same data displayed in the CSD Console dashboard, script list, form fields, and network views.

What it proves: CSD is detecting and cataloging scripts running on the protected domain.

Terminal window
NOW=$(date +%s)
START=$(( NOW - 604800 ))
curl -s -X POST \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
-H "Content-Type: application/json" \
-d "{
\"startTime\": \"${START}\",
\"endTime\": \"${NOW}\"
}" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/scripts" \
| jq -r '
["SCRIPT", "RISK", "STATUS", "FIELDS", "USERS"],
(.scripts[] | [
(.script_name | if length > 50 then .[:47] + "..." else . end),
.risk_level,
.status,
(.form_fields_read // 0),
(.affected_users_count // 0)
])
| @tsv' | column -t
ResultMeaning
PASS — scripts listed with risk levelsCSD is actively monitoring scripts
FAIL — empty arraySee Troubleshooting: Empty Scripts Array

What it proves: CSD has detected script source domains and classified them by status.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/detected_domains" \
| jq '{
summary: {
action_needed: .domain_summary.actionNeededCount.count,
mitigated: .domain_summary.mitigatedDomains.count,
allowed: .domain_summary.allowedDomains.count,
total: .domain_summary.totalDomains.count
},
domains: [.domains_list[] | {
domain: .domain,
category: .category,
status: .status,
first_seen: (.firstSeenDate | tonumber | todate),
latest_seen: (.latestSeenDate | tonumber | todate)
}]
}'
ResultMeaning
PASStotal > 0 with domains listedCSD is tracking script domains
WARNaction_needed > 0Some domains need review
FAIL — empty responseNo telemetry data — check CSD-5

What it proves: Shows the distribution of allowed vs. mitigated domains and whether policy is consistent.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/detected_domains" \
| jq '{
action_needed: .domain_summary.actionNeededCount.count,
mitigated: .domain_summary.mitigatedDomains.count,
allowed: .domain_summary.allowedDomains.count,
total: .domain_summary.totalDomains.count,
domains_needing_action: [.domains_list[] | select(.status == "AN") | .domain]
}'
ResultMeaning
PASSaction_needed: 0All domains have been reviewed and classified
WARNaction_needed > 0Domains listed in domains_needing_action require review

What it proves: CSD has detected form fields that scripts are reading, with sensitivity classification.

Terminal window
NOW=$(date +%s)
START=$(( NOW - 604800 ))
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/formFields?startTime=${START}&endTime=${NOW}" \
| jq -r '
["FIELD", "SENSITIVITY", "SCRIPTS"],
(.form_fields[] | [
.name,
.analysis,
(.scripts_count // 0)
])
| @tsv' | column -t
ResultMeaning
PASS — form fields listed with sensitivityCSD is tracking form field access
INFO — empty arrayNo form fields detected (expected if no forms on the site)

What it proves: Detailed information about a specific script including risk, behaviors, and network interactions.

First, get a script ID from TEL-1, then query its details:

Terminal window
SCRIPT_ID="your-script-id"
# Overview (risk level, type, source domain)
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/scripts/${SCRIPT_ID}/dashboard" \
| jq '{script_name, risk_level, type, source_domain, status}'
# Behaviors over time
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/scripts/${SCRIPT_ID}/behaviors" \
| jq '.behaviors'
# Network interactions (domains the script communicates with)
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/scripts/${SCRIPT_ID}/networkInteractions" \
| jq '.network_interactions'

What it proves: Lists users impacted by a specific script, showing the scope of exposure.

Terminal window
SCRIPT_ID="your-script-id"
NOW=$(date +%s)
START=$(( NOW - 604800 ))
curl -s -X POST \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
-H "Content-Type: application/json" \
-d "{
\"startTime\": \"${START}\",
\"endTime\": \"${NOW}\"
}" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/scripts/${SCRIPT_ID}/affectedUsers" \
| jq -r '
["IP_ADDRESS", "DEVICE_ID", "GEO", "CHANNEL", "USER_AGENT"],
(.affected_users[] | [
.ip_address,
(.device_id | if length > 12 then .[:12] + "..." else . end),
.geolocation,
.channel,
(.user_agent | if length > 30 then .[:27] + "..." else . end)
])
| @tsv' | column -t

What it proves: Aggregated risk distribution across all detected scripts.

Terminal window
NOW=$(date +%s)
START=$(( NOW - 604800 ))
curl -s -X POST \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
-H "Content-Type: application/json" \
-d "{
\"startTime\": \"${START}\",
\"endTime\": \"${NOW}\"
}" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/scripts" \
| jq '[.scripts[] | .risk_level] | group_by(.) | map({risk_level: .[0], count: length}) | sort_by(-.count)'
ResultMeaning
PASS — all No RiskNo risky scripts detected
WARNLow Risk or High Risk presentReview flagged scripts in TEL-5

What it proves: CSD telemetry data is recent, confirming active monitoring.

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/detected_domains" \
| jq '{
total_domains: .domain_summary.totalDomains.count,
latest_update: (.domain_summary.totalDomains.lastUpdated // "unknown"),
most_recent_domain: (.domains_list | sort_by(.latestSeenDate) | last | {
domain: .domain,
latest_seen: (.latestSeenDate | tonumber | todate)
})
}'
ResultMeaning
PASSlatest_seen is within the last 24 hoursTelemetry is actively collecting data
WARNlatest_seen is older than 24 hoursTraffic may have stopped or CSD processing is delayed

Run a single command that checks all critical health indicators across every layer and produces a summary table:

Terminal window
echo "=== CSD Verification Dashboard ==="
echo ""
# Layer 1: DNS
DNS_A=$(dig +short xF5XC_DOMAINNAMEx A | head -1)
DNS_ACME=$(dig +short _acme-challenge.xF5XC_DOMAINNAMEx CNAME | head -1)
# Layers 2-3: LB + TLS
LB=$(curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http")
LB_HTTPS=$(curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https")
LB_STATE=$(echo "$LB" | jq -r '.spec.state // "UNKNOWN"')
CERT_STATE=$(echo "$LB_HTTPS" | jq -r '.spec.cert_state // "UNKNOWN"')
CSD_ON_LB=$(echo "$LB" | jq -r 'if .spec.client_side_defense then "ENABLED" else "DISABLED" end')
DOMAINS=$(echo "$LB" | jq -r '.spec.domains | join(", ")')
ROUTE_POOL=$(echo "$LB" | jq -r '[.spec.default_route_pools[] | .pool.name] | join(", ")')
# Layer 6: CSD status
CSD=$(curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/status")
CSD_CONFIGURED=$(echo "$CSD" | jq -r '.isConfigured // false')
CSD_ENABLED=$(echo "$CSD" | jq -r '.isEnabled // false')
# Layer 6: JS config
JS_TAG=$(curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/js_configuration" \
| jq -r 'if (.scriptTag | length) > 0 then "PRESENT" else "MISSING" end')
# Layer 7: Traffic (last 24h)
TRAFFIC=$(curl -s -X POST \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
-H "Content-Type: application/json" \
-d "{
\"start_time\": \"$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-24H +%Y-%m-%dT%H:%M:%SZ)\",
\"end_time\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
}" \
"xF5XC_API_URLx/api/data/namespaces/xF5XC_NAMESPACEx/access_logs/aggregation" \
| jq -r '.total_hits // "0"')
printf "%-28s %s\n" "CHECK" "STATUS"
printf "%-28s %s\n" "----------------------------" "----------------------------"
printf "%-28s %s\n" "[DNS] A Record" "${DNS_A:-NOT_FOUND}"
printf "%-28s %s\n" "[DNS] ACME CNAME" "${DNS_ACME:-NOT_FOUND}"
printf "%-28s %s\n" "[TLS] Certificate" "$CERT_STATE"
printf "%-28s %s\n" "[LB] State" "$LB_STATE"
printf "%-28s %s\n" "[LB] Domains" "$DOMAINS"
printf "%-28s %s\n" "[LB] Route Pool" "$ROUTE_POOL"
printf "%-28s %s\n" "[LB] CSD on LB" "$CSD_ON_LB"
printf "%-28s %s\n" "[CSD] Configured (tenant)" "$CSD_CONFIGURED"
printf "%-28s %s\n" "[CSD] Enabled" "$CSD_ENABLED"
printf "%-28s %s\n" "[CSD] JS Script Tag" "$JS_TAG"
printf "%-28s %s\n" "[Traffic] Requests (24h)" "$TRAFFIC"

FieldTypeDescription
@timestampstringRequest timestamp (ISO 8601). Note the @ prefix — access with .["@timestamp"] in jq
methodstringHTTP method (GET, POST, etc.)
req_pathstringRequest URI path
rsp_codestringHTTP response status code as a string (e.g., "200", "404")
rsp_code_classstringStatus code class (2xx, 3xx, 4xx, 5xx, or downstream_remote_disconnect)
src_ipstringClient source IP address
dst_ipstringDestination (VIP) IP address
domainstringRequest Host header value
user_agentstringClient User-Agent string
rsp_sizestringResponse body size in bytes (returned as string)
req_sizestringRequest body size in bytes (returned as string)
duration_with_data_tx_delaystringTotal request duration in seconds (returned as string, e.g., "0.024219")
csd_js_injectionstring"true" when CSD JavaScript was injected (only present when active)
FieldEndpointDescription
isConfiguredstatusCSD enabled at tenant level
isEnabledstatusCSD active for this namespace
scripts[]scriptsArray of detected script objects
.script_namescriptsFull URL of the JavaScript file
.risk_levelscriptsRisk level (No Risk, Low Risk, High Risk)
.statusscriptsAN (Action Needed) or NA (No Action Needed)
.form_fields_readscriptsNumber of form fields the script reads
.affected_users_countscriptsNumber of unique users/sessions affected
domain_summarydetected_domainsCounts by status: .actionNeededCount.count, .mitigatedDomains.count, .allowedDomains.count, .totalDomains.count (each has .count and .lastUpdated)
domains_list[]detected_domainsArray of detected domain objects with .domain, .status, .category, .firstSeenDate, .latestSeenDate (epoch seconds as strings)
form_fields[]formFieldsArray of detected form field objects
.analysisformFieldsSensitivity classification (Sensitive, Not Sensitive)

If TV-1 returns 0:

  1. Check DNS resolution — verify the domain resolves to the LB VIP:

    Terminal window
    dig +short xF5XC_DOMAINNAMEx A

    If empty, DNS is not configured. See API Automation — Step 4.

  2. Check LB state — the load balancer must be VIRTUAL_HOST_READY:

    Terminal window
    curl -s \
    -H "Authorization: APIToken xF5XC_API_TOKENx" \
    "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \
    | jq '.spec.state'
  3. Send a test request — generate traffic to confirm end-to-end connectivity:

    Terminal window
    curl -sv "http://xF5XC_DOMAINNAMEx/" 2>&1 | head -20

    Look for a successful HTTP response. Use https:// only if the HTTPS LB certificate is valid.

If DNS-4 returns false, automatic record creation is disabled even though F5 XC is the authoritative DNS provider. This is a common misconfiguration that causes both the A record and ACME CNAME to be missing, which blocks the load balancer from reaching VIRTUAL_HOST_READY and the certificate from being issued.

To enable managed records, update the DNS zone configuration to set allow_http_lb_managed_records: true. See API Automation — Option A: F5 XC Managed DNS for the API call.

The load balancer is waiting for a DNS A record pointing to its VIP. See LB Stuck in VIRTUAL_HOST_PENDING_A_RECORD for detailed resolution steps.

Certificate Stuck in PreDomainChallengePending

Section titled “Certificate Stuck in PreDomainChallengePending”

The automatic TLS certificate requires an ACME challenge record. The method depends on your DNS provider:

F5 XC Managed DNS: Enable allow_http_lb_managed_records on the DNS zone (DNS-4). The platform auto-creates a TXT-based ACME challenge record in the x-ves-io-managed RR set group. If the certificate remains stuck after enabling managed records, delete and recreate the load balancer to trigger a fresh certificate request.

External DNS: Create a CNAME record at your DNS provider:

_acme-challenge.xF5XC_DOMAINNAMEx CNAME *.autocerts.ves.volterra.io

Verify the record exists:

Terminal window
dig +short _acme-challenge.xF5XC_DOMAINNAMEx CNAME
dig +short _acme-challenge.xF5XC_DOMAINNAMEx TXT

If both are empty, the ACME record is not configured. Certificate provisioning takes 5–10 minutes after the record is in place. Check the LB error status for specific ACME validation errors:

Terminal window
curl -s \
-H "Authorization: APIToken xF5XC_API_TOKENx" \
"xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \
| jq '[.status[]? | select(.virtual_host_status != null) | .virtual_host_status.error_description]'

CSD must be enabled at the tenant level by an F5 XC administrator. This is a tenant-wide setting that cannot be configured via the namespace API. Contact your administrator to enable Client-Side Defense.

If TEL-1 returns an empty array:

  1. Check the protected domain — CSD monitors scripts only on registered domains:

    Terminal window
    curl -s \
    -H "Authorization: APIToken xF5XC_API_TOKENx" \
    "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains" \
    | jq '.items[] | {name, namespace}'
  2. Verify JS injection — confirm the CSD script tag is being injected into pages:

    Terminal window
    curl -s "http://xF5XC_DOMAINNAMEx/" | grep -oE '(zeronaught|shape)\.com[^"]*' | head -1

    If no match, CSD JavaScript is not being injected. Check that the LB has client_side_defense enabled.

  3. Allow processing time — after first enabling CSD or registering a new protected domain, script detection can take 5–15 minutes. Generate traffic to the site and wait before re-checking.

PeriodEpoch offsetISO 8601 (Linux)ISO 8601 (macOS)
1 hour$(( $(date +%s) - 3600 ))date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZdate -u -v-1H +%Y-%m-%dT%H:%M:%SZ
24 hours$(( $(date +%s) - 86400 ))date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZdate -u -v-24H +%Y-%m-%dT%H:%M:%SZ
7 days$(( $(date +%s) - 604800 ))date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZdate -u -v-7d +%Y-%m-%dT%H:%M:%SZ
30 days$(( $(date +%s) - 2592000 ))date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZdate -u -v-30d +%Y-%m-%dT%H:%M:%SZ
LayerTestsWhat it covers
1. DNS ResolutionDNS-1 through DNS-4A record, ACME CNAME, nameserver authority, managed records
2. TLS CertificateTLS-1 through TLS-4Cert state, cert details, live handshake, ACME target
3. HTTP Load BalancerLB-1 through LB-6State, domains, CSD flag, route pools, deployment, summary
4. Origin PoolOP-1 through OP-4Config, TLS mode, HC association, origin connectivity
5. Health CheckHC-1 through HC-2HC config, list all HCs
6. CSD ConfigurationCSD-1 through CSD-5Tenant status, JS tag, protected domains, live injection
7. Traffic VerificationTV-1 through TV-5Request count, status codes, samples, JS in logs, E2E test
8. CSD TelemetryTEL-1 through TEL-8Scripts, domains, policy, form fields, deep dive, users, risk, freshness
DiagnosticEndpointMethodTime Format
Request count/api/data/namespaces/\{ns\}/access_logs/aggregationPOSTISO 8601
Status codes/api/data/namespaces/\{ns\}/access_logsPOSTISO 8601
Recent requests/api/data/namespaces/\{ns\}/access_logsPOSTISO 8601
LB state/api/config/namespaces/\{ns\}/http_loadbalancers/\{name\}GETNone
Origin pool/api/config/namespaces/\{ns\}/origin_pools/\{name\}GETNone
Healthcheck/api/config/namespaces/\{ns\}/healthchecks/\{name\}GETNone
DNS zone/api/config/dns/namespaces/system/dns_zones/\{zone\}GETNone
CSD status/api/shape/csd/namespaces/\{ns\}/statusGETNone
JS configuration/api/shape/csd/namespaces/\{ns\}/js_configurationGETNone
Protected domains/api/shape/csd/namespaces/\{ns\}/protected_domainsGETNone
Script list/api/shape/csd/namespaces/\{ns\}/scriptsPOSTEpoch seconds
Detected domains/api/shape/csd/namespaces/\{ns\}/detected_domainsGETNone
Form fields/api/shape/csd/namespaces/\{ns\}/formFieldsGETEpoch seconds (query params)
Script details/api/shape/csd/namespaces/\{ns\}/scripts/\{id\}/dashboardGETNone
Script behaviors/api/shape/csd/namespaces/\{ns\}/scripts/\{id\}/behaviorsGETNone
Script network/api/shape/csd/namespaces/\{ns\}/scripts/\{id\}/networkInteractionsGETNone
Affected users/api/shape/csd/namespaces/\{ns\}/scripts/\{id\}/affectedUsersPOSTEpoch seconds