Share-link standard §4.3.19 supports extra= (URL-encoded XHttpObject JSON),
and passwall exposes it as XHTTP Extra. Verified end-to-end with real xray
v26.3.27 + caddy: matching client+server extra → 204 through the full TLS
h2 chain. Default off because a client that ignores the extra param gets
400 from a padding-enabled server.
Also correct the previous claim that links can't carry extra.
Co-Authored-By: Claude <noreply@anthropic.com>
End-to-end matrix with real xray v26.3.27 + caddy:
- server with extra padding + plain client: 400 (direct AND via caddy)
- server without extra + plain client: 204 (direct AND via caddy, TLS h2)
Padding placement/key names must match on both ends, but share links cannot
carry these params and mainstream clients can't configure them — so a
server-side-only extra block rejects every real client. Removed from the
template along with XPADDING_HEADER/XPADDING_KEY.
Co-Authored-By: Claude <noreply@anthropic.com>
Root-caused with live probes: WS upgrade to /scilad returned 404 on both
direct and CDN paths. handle_path strips the prefix before proxying, but
xray wsSettings.path=/scilad expects it intact. Replaced with a site-level
@websockets matcher (path + Upgrade headers) + path-preserving reverse_proxy
in the main block; deleted the redundant handle_path /scilad* in the CDN
block (it also shadowed reverse_proxy @wspaths into dead code).
Verified with real caddy + mock upstream: WS handshake gets 101 and the
upstream receives /scilad unstripped.
Co-Authored-By: Claude <noreply@anthropic.com>
Go's native TLS ClientHello fingerprint is a cheap high-confidence proxy
signal (JA3/JA4); make the uTLS fingerprint explicit on ws/xhttp links
(direct + CDN) instead of relying on client-side defaults. Also assert
the ws-cdn link and fp=chrome coverage in tests.
Co-Authored-By: Claude <noreply@anthropic.com>
Caddy only listens on loopback 8003 behind Xray REALITY fallback (pure TCP),
so advertising h3 in TLS ALPN is both useless and harmful: a client offering
h3 gets h3 negotiated over TCP and the connection hangs (verified with
openssl s_client -alpn h3,h2 -> 'ALPN protocol: h3'). Drop h3 from both
site blocks. Also print the WS CDN link (CDN site block already routes
/scilad) and assert 'alpn h3' never reappears.
Co-Authored-By: Claude <noreply@anthropic.com>
Caddy injects runtime auto-redirect routes (host-matched, ahead of the
catch-all http:// site, not excluding ACME challenge paths) for domains
it manages certs for — invisible in static 'caddy adapt' output, so the
previous live test could not reproduce it. hy2's tokens got 308'd to 443
(LE: 'Invalid response from https://DOMAIN/...: 404' via masquerade).
Verified bidirectionally with real caddy v2.11.4: without the option the
challenge path gets 308 (exact production repro), with it 200 from hy2.
Live test now extracts the global block + http block + a tls-internal
managed site block to reproduce production conditions permanently.
Co-Authored-By: Claude <noreply@anthropic.com>
Verified with real caddy v2.11.4: fixed Caddyfile proxies
/.well-known/acme-challenge/* to hy2 (200 from mock) and 301s everything
else; the old bare-redir variant 301s the challenge (exact production
bug reproduction). tests/run_tests.sh now runs this live route test when
a caddy binary is on PATH. self_check additionally probes the challenge
path post-deploy and warns if it gets redirected.
Co-Authored-By: Claude <noreply@anthropic.com>
www.people.com answers every non-browser request with a Cloudflare JS
challenge (cf-mitigated: challenge) — the masquerade instantly fails
active probing, and the open reverse proxy gets the VPS IP flagged by
CF for scraping. engineersblog.net serves real 200 content without a
challenge; using the same target as the Xray fallback makes 443 and
8443 present the same site, which strengthens the disguise.
Co-Authored-By: Claude <noreply@anthropic.com>
Caddyfile directive order puts redir before handle; the matcherless
'redir https://...' in the http:// block caught EVERY port-80 request
including /.well-known/acme-challenge/*, so LE's hy2 validation got a
301 to 443 (visible as 'Fetching https://...' + tls internal error) and
hysteria-server FATALed on startup. The redir now carries a
not-path matcher for the challenge prefix, making evaluation
order-independent (same pattern the main site block already used via
@notpaths).
Co-Authored-By: Claude <noreply@anthropic.com>
The function's last command was '[ -n "$DRY_RUN" ] && info ...', which returns
1 when DRY_RUN is empty; as the function's exit status that triggered set -e
on the very first pipeline stage — zero output, instant exit. Rewritten as an
if block, comment added, awk scan confirms no other function ends with an
&&/|| list, and a regression test executes the real preflight body with
mocked id/apt-get outside DRY_RUN.
Co-Authored-By: Claude <noreply@anthropic.com>
Install is already idempotent-upgrade (apt install caddy / xray install script /
get.hy2.sh all fetch latest); upgrade mode additionally requires an existing
setup.env (dies instead of accidentally installing) and never prompts.
Legacy setup.env without HY2_CERT_MODE infers dns when CF_API_TOKEN present,
so upgrades keep wildcard certs instead of silently switching to http mode.
Co-Authored-By: Claude <noreply@anthropic.com>
SKIP_CADDY=1 + HY2_CERT_MODE=http now checks port 80/TCP is free (ss) and
lets hy2 answer HTTP-01 directly (altPort 80, all interfaces) instead of
erroring out; still dies if 80 is occupied.
Co-Authored-By: Claude <noreply@anthropic.com>
hy2 certmagic HTTP-01 listens only on 127.0.0.1:9180 (altPort); Caddy's
port-80 block reverse-proxies /.well-known/acme-challenge/ to it. Non-wildcard
DOMAIN cert, auto-renewed and hot-applied, no DNS API token needed, no new
public ports. TLS-ALPN is unusable (CA only connects to TCP 443 = Xray) and
reusing Caddy's cert files needs permission/copy hooks, so HTTP-01 is the
minimal stable default. HY2_CERT_MODE=dns keeps Cloudflare DNS-01 wildcard
as fallback (CF_API_TOKEN required only in dns mode).
Co-Authored-By: Claude <noreply@anthropic.com>