#!/bin/bash
# Add a domain to the egress firewall allowlist of a running claude-sandbox
# container, from outside it. The sandbox itself has no privilege to do this
# (node has no sudo/setuid path back to root - see docker/claude-sandbox's
# security notes); if a sandboxed agent hits a blocked domain it actually
# needs, it should tell you the exact command to run here rather than try
# to work around the firewall.
#
# Usage: claude-sandbox-allow <domain> [--global] [--container <name>]
#   claude-sandbox-allow crates.io                # this running session only
#   claude-sandbox-allow crates.io --global       # + persist for future sessions
set -euo pipefail

domain=""
global=0
container=""

while [ "$#" -gt 0 ]; do
    case "$1" in
        --global) global=1; shift ;;
        --container) container="$2"; shift 2 ;;
        -*) echo "Unknown option: $1" >&2; exit 1 ;;
        *)
            if [ -n "$domain" ]; then
                echo "Usage: claude-sandbox-allow <domain> [--global] [--container <name>]" >&2
                exit 1
            fi
            domain="$1"
            shift
            ;;
    esac
done

if [ -z "$domain" ]; then
    echo "Usage: claude-sandbox-allow <domain> [--global] [--container <name>]" >&2
    exit 1
fi

# Validated up front, not just passed as a positional arg below: this also
# has to be a well-formed hostname to be a valid dnsmasq server=/domain/...
# line (e.g. no embedded "/"), on top of ruling out shell metacharacters. A
# sandboxed agent can suggest this exact command to the user to copy-paste
# (see sandbox-context.sh), so a prompt-injected agent handing back a
# malicious "domain" is a real path here, not just a theoretical one.
if [[ ! "$domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}\.)*[a-zA-Z0-9-]{1,63}$ ]]; then
    echo "Not a valid domain: $domain" >&2
    exit 1
fi

if [ -z "$container" ]; then
    containers=$(docker ps --filter "label=com.docker.compose.project=claude-sandbox" --filter "status=running" --format '{{.Names}}')
    count=$(echo "$containers" | grep -c . || true)
    if [ "$count" -eq 0 ]; then
        echo "No running claude-sandbox container found." >&2
        exit 1
    elif [ "$count" -gt 1 ]; then
        echo "Multiple running claude-sandbox containers found; specify one with --container:" >&2
        echo "$containers" >&2
        exit 1
    fi
    container="$containers"
fi

# The DNS proxy (dns-proxy.sh) NXDOMAINs anything not in its allowlist, so
# this has to happen *before* resolving the domain below - otherwise the dig
# right after this would itself get blocked. Append a server= line to
# dnsmasq's servers-file (idempotent - skip if already present from an
# earlier call) and SIGHUP it to reload just that file, no restart needed.
# $domain is passed as a positional arg to the inner script (`$1`, inside a
# single-quoted heredoc) rather than interpolated into the script text - it's
# validated above, but this keeps it that way even if the validation regex
# above is ever loosened later.
docker exec -u root "$container" bash -c '
    domain="$1"
    entry="server=/${domain}/127.0.0.11"
    grep -qxF "$entry" /etc/dnsmasq-sandbox-servers.conf 2>/dev/null || echo "$entry" >> /etc/dnsmasq-sandbox-servers.conf
    kill -HUP "$(cat /var/run/dnsmasq-sandbox.pid)"
' -- "$domain"

echo "Resolving $domain inside $container..."
ips=$(docker exec "$container" dig +short A "$domain" | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' || true)
if [ -z "$ips" ]; then
    echo "Failed to resolve $domain inside the container." >&2
    exit 1
fi

for ip in $ips; do
    echo "Allowing $ip ($domain)"
    docker exec -u root "$container" ipset add allowed-domains "$ip" -exist
done

echo "$domain allowed for the running session ($container)."

if [ "$global" -eq 1 ]; then
    profile_file="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/config/profile"
    if [ -f "$profile_file" ] && grep -q '^export ALLOWED_DOMAINS=' "$profile_file"; then
        current=$(sed -nE 's/^export ALLOWED_DOMAINS="(.*)"$/\1/p' "$profile_file")
        if [[ " $current " == *" $domain "* ]]; then
            echo "$domain already in the global ALLOWED_DOMAINS (config/profile)."
        else
            new="${current:+$current }$domain"
            sed -i "s|^export ALLOWED_DOMAINS=.*|export ALLOWED_DOMAINS=\"$new\"|" "$profile_file"
            echo "Added $domain to config/profile's ALLOWED_DOMAINS (source your shell, or open a new one, for future sessions to pick it up)."
        fi
    else
        echo "Couldn't find 'export ALLOWED_DOMAINS=' in $profile_file to update." >&2
        exit 1
    fi
fi
