{ admin off email {$ACME_EMAIL:adrianodalpastro@tielogic.com} # Plugin mholt/caddy-ratelimit order rate_limit before basicauth } cerbero-mcp.tielogic.xyz { log { output stdout format json } # ───── Security headers ───── header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" Referrer-Policy "no-referrer" -Server } # ───── IP allowlist su endpoint write ───── # WRITE_ALLOWLIST: CIDR space-separated (es. "1.2.3.4/32 5.6.7.0/24"). # Default 127.0.0.1/32 — fail-closed se non configurato. @writes_blocked { path_regexp ^/mcp-[a-z]+/tools/(place_|cancel_|set_|close_|transfer_|amend_|switch_) not remote_ip {$WRITE_ALLOWLIST:127.0.0.1/32 ::1/128 172.16.0.0/12} } respond @writes_blocked "forbidden: source ip not in allowlist" 403 # ───── Rate limit ───── # Reads: 60 req/min/IP, writes: 10 req/min/IP (sliding window). rate_limit { zone reads { match { not path_regexp ^/mcp-[a-z]+/tools/(place_|cancel_|set_|close_|transfer_|amend_|switch_) } key {remote_ip} events 60 window 1m } zone writes { match { path_regexp ^/mcp-[a-z]+/tools/(place_|cancel_|set_|close_|transfer_|amend_|switch_) } key {remote_ip} events 10 window 1m } } # ───── Reverse proxy ───── handle_path /mcp-deribit/* { reverse_proxy mcp-deribit:9011 } handle_path /mcp-bybit/* { reverse_proxy mcp-bybit:9019 } handle_path /mcp-hyperliquid/* { reverse_proxy mcp-hyperliquid:9012 } handle_path /mcp-alpaca/* { reverse_proxy mcp-alpaca:9020 } handle_path /mcp-macro/* { reverse_proxy mcp-macro:9013 } handle_path /mcp-sentiment/* { reverse_proxy mcp-sentiment:9014 } # Landing page statica handle { root * /srv file_server } }