#!/usr/bin/env bash # Add Cloudflare Transform Rule to set Content-Security-Policy with unsafe-eval for explorer.d-bis.org # Requires: CLOUDFLARE_API_TOKEN or (CLOUDFLARE_EMAIL + CLOUDFLARE_API_KEY) # Zone: d-bis.org (CLOUDFLARE_ZONE_ID_D_BIS_ORG) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" [ -f "$PROJECT_ROOT/.env" ] && source "$PROJECT_ROOT/.env" 2>/dev/null || true ZONE_ID="${CLOUDFLARE_ZONE_ID_D_BIS_ORG:-${CLOUDFLARE_ZONE_ID:-}}" if [ -z "$ZONE_ID" ]; then echo "CLOUDFLARE_ZONE_ID required"; exit 1; fi # Auth - use token if available, else email+key if [ -n "${CLOUDFLARE_API_TOKEN:-}" ]; then CURL_AUTH=(-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN") elif [ -n "${CLOUDFLARE_EMAIL:-}" ] && [ -n "${CLOUDFLARE_API_KEY:-}" ]; then CURL_AUTH=(-H "X-Auth-Email: $CLOUDFLARE_EMAIL" -H "X-Auth-Key: $CLOUDFLARE_API_KEY") else echo "Need CLOUDFLARE_API_TOKEN or CLOUDFLARE_EMAIL+CLOUDFLARE_API_KEY"; exit 1 fi CSP="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'self' 'unsafe-inline' https: data:; font-src 'self' https: data:; img-src 'self' data: https: blob:; connect-src 'self' https: wss: ws:; media-src 'self' https: data:; object-src 'none'; base-uri 'self'; form-action 'self' https:; frame-ancestors 'none'; upgrade-insecure-requests" # 1. List zone rulesets to find http_response_headers_transform RULESETS=$(curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \ "${CURL_AUTH[@]}" -H "Content-Type: application/json") RULESET_ID=$(echo "$RULESETS" | jq -r '.result[] | select(.phase == "http_response_headers_transform") | .id' | head -1) # 2. If no ruleset exists, create one if [ -z "$RULESET_ID" ] || [ "$RULESET_ID" = "null" ]; then CREATE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \ "${CURL_AUTH[@]}" -H "Content-Type: application/json" \ -d "{\"name\":\"Zone Response Headers Transform\",\"kind\":\"zone\",\"phase\":\"http_response_headers_transform\",\"rules\":[]}") RULESET_ID=$(echo "$CREATE" | jq -r '.result.id') if [ -z "$RULESET_ID" ] || [ "$RULESET_ID" = "null" ]; then echo "Failed to create ruleset: $(echo "$CREATE" | jq -r '.errors')"; exit 1 fi echo "Created ruleset $RULESET_ID" fi # 3. Get current rules (if any) and add our rule CURRENT=$(curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$RULESET_ID" "${CURL_AUTH[@]}") EXISTING_RULES=$(echo "$CURRENT" | jq -r '.result.rules // []') # Remove any existing explorer CSP rule NEW_RULES=$(echo "$EXISTING_RULES" | jq '[.[] | select(.ref != "explorer_csp_unsafe_eval")]') # Add our rule (use jq for safe JSON) RULE=$(jq -n --arg csp "$CSP" '{ ref: "explorer_csp_unsafe_eval", expression: "(http.host eq \"explorer.d-bis.org\")", description: "Explorer CSP with unsafe-eval for ethers.js v5", action: "rewrite", action_parameters: { headers: { "Content-Security-Policy": { operation: "set", value: $csp } } } }') NEW_RULES=$(echo "$NEW_RULES" | jq --argjson rule "$RULE" '. + [$rule]') # 4. Update ruleset RES=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$RULESET_ID" \ "${CURL_AUTH[@]}" -H "Content-Type: application/json" \ -d "{\"rules\":$NEW_RULES}") if echo "$RES" | jq -e '.success' >/dev/null 2>&1; then echo "Cloudflare Transform Rule added for explorer.d-bis.org" else echo "Failed: $(echo "$RES" | jq -r '.errors')"; exit 1 fi