#899·gost

Whitelist bypass + sniffing: domain allowlist broken since 3.3.0

Author: xt-yinCreated Aug 31, 2026Updated Sep 2, 2026

Hi, thank you for the great tool.

I was trying to upgrade gost to 3.3.0 and found an issue with whitelist bypass + sniffing. Here is how to reproduce the issue.

Reproduce

bash
#!/usr/bin/env bash
# Minimal repro: whitelist bypass + sniffing on a transparent (red) proxy.
#
# Since go-gost/x@fe394a8 ("check bypass on dstAddr before sniffing",
# x >= v0.13.12, shipped in gost 3.3.0), the bypass is evaluated against the
# destination IP *before* SNI sniffing runs. In whitelist mode the resolved IP
# is never in the list, so the connection is rejected before the sniffed host
# (e.g. example.com) can match. Domain entries in a whitelist bypass are
# effectively dead for HTTPS traffic.
#
# Expected:  https://example.com works on builds with go-gost/x < v0.13.12
#            (e.g. 3.2.7-nightly.20260602), fails on 3.3.0.
#            (3.2.6 stable predates the nft tooling in the image, so the
#            nightly is used as the good baseline.)
#
# Usage: ./gost-issue-repro.sh [image ...]
set -u

IMAGES=("$@")
if [ ${#IMAGES[@]} -eq 0 ]; then
  IMAGES=(gogost/gost:3.2.7-nightly.20260602 gogost/gost:3.3.0)
fi

# Single self-contained config: transparent proxy on :12345, whitelist bypass
# containing only a domain (no IPs), TLS sniffing enabled.
read -r -d '' GOST_YAML <<'EOF' || true
log:
  level: debug
services:
  - name: transparent-egress
    addr: ":12345"
    bypass: allowlist
    metadata:
      so_mark: 114514
    handler:
      type: red
      metadata:
        sniffing: true
        sniffing.timeout: 5s
        sniffing.fallback: true
    listener:
      type: red
bypasses:
  - name: allowlist
    whitelist: true
    matchers:
      - example.com
EOF

# Runs inside the container: redirect all TCP egress into gost, start gost,
# then try to fetch https://example.com (its IP is NOT in the whitelist; only
# the domain is, so this only works when SNI sniffing drives the decision).
read -r -d '' INNER <<'EOF' || true
printf '%s' "$GOST_YAML" > /tmp/gost.yaml
nft add table inet repro
nft add chain inet repro output '{ type nat hook output priority dstnat; policy accept; }'
nft add rule inet repro output meta mark 114514 return
nft add rule inet repro output fib daddr type local return
nft add rule inet repro output meta l4proto tcp redirect to :12345
gost -C /tmp/gost.yaml &
sleep 1
wget -T 8 -O /dev/null https://example.com/ 2>&1 | tail -2
sleep 1
EOF

overall=0
for image in "${IMAGES[@]}"; do
  echo "=== $image ==="
  # Neutralize proxy env vars the docker client may inject; the repro needs
  # direct egress only.
  output=$(docker run --rm --cap-add NET_ADMIN \
    -e http_proxy= -e https_proxy= -e all_proxy= \
    -e HTTP_PROXY= -e HTTPS_PROXY= -e ALL_PROXY= \
    -e GOST_YAML="$GOST_YAML" \
    --entrypoint /bin/sh \
    "$image" -c "$INNER" 2>&1)
  echo "$output" | grep -E "saved|reset|refused|timed out|bad address" | head -2
  # The tell-tale: a whitelist bypass decision against a bare IP (pre-sniffing).
  echo "$output" | grep -o '"bypass: [0-9.]*:443, whitelist: true"' | head -1
  if echo "$output" | grep -q "saved"; then
    echo "RESULT: OK (domain allowlisted via SNI sniffing)"
  else
    echo "RESULT: BROKEN (allowlisted domain rejected before sniffing)"
    overall=1
  fi
  echo
done
exit "$overall"