Release v0.51.260 — Release IB (stage-r8) (#3614)
Some checks failed
Release & Docker / release (push) Has been cancelled
Some checks failed
Release & Docker / release (push) Has been cancelled
## Release v0.51.260 — Release IB (stage-r8) Un-held safety fixes (author resolved my earlier hold findings; re-reviewed fresh) + a clean fix batch. 6 PRs. ### Fixed | Issue/PR | Author | Fix | |----------|--------|-----| | #3535 (#3538) | @rodboev | **Self-update recovers from a stash-pop conflict without data loss.** Was a BRICK bug (`git reset --merge` + `git stash drop` discarded local mods while reporting success). Now keeps the stash, returns `ok:false` + "preserved in `stash@{0}`", no restart on conflict. *(was held — fix verified)* | | #1909 s3 (#3562) | @rodboev | **Auth `Secure` cookie no longer locks out plain-HTTP LAN/Tailscale users.** Secure now keys only on real TLS evidence (env / TLS socket / opt-in `TRUST_FORWARDED_PROTO`); non-loopback plain-HTTP is no longer force-Secure. SameSite back to `Lax`. *(was held — fix verified)* | | #2785 (#3559) | @franksong2702 | Clearer cron/gateway diagnostics for single-container Docker (gateway configured, no daemon → jobs silently don't fire). | | #3555 | @lambyangzhao | Long TTS responses chunked at sentence boundaries (works around the browser's ~32K silent-truncation). | | #3340 (#3342) | @rly09 | Persistent-state toast when a turn has saved memory / created-updated a skill. | | #3533 | @franksong2702 | `/reload-mcp` marked `cli_only` so the WebUI doesn't dispatch it as an LLM prompt. | ### Gate - Full pytest suite: **7681 passed, 0 failed** - ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN - Codex (regression): **SAFE TO SHIP** — confirmed the stash-conflict path never drops the stash / never restarts on conflict, auth Secure handles LAN-HTTP correctly with no header-forgery hole, `/reload-mcp` allowlisted, state-toast has a real backend writer + active-session guard, diagnostics leak no paths, TTS chunking preserves order. Co-authored-by: rodboev <rodboev@users.noreply.github.com> Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com> Co-authored-by: lambyangzhao <lambyangzhao@users.noreply.github.com> Co-authored-by: rly09 <rly09@users.noreply.github.com>
This commit is contained in:
41
api/auth.py
41
api/auth.py
@@ -540,21 +540,36 @@ def check_auth(handler, parsed) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _is_loopback(addr: str) -> bool:
|
||||
"""Return True if *addr* is a loopback address (127.x.x.x, ::1, or ::ffff:127.x.x.x)."""
|
||||
import ipaddress as _ipaddress
|
||||
try:
|
||||
ip = _ipaddress.ip_address(addr)
|
||||
if ip.is_loopback:
|
||||
return True
|
||||
# Python < 3.12: is_loopback is False for ::ffff:127.x.x.x (gh-117566)
|
||||
if hasattr(ip, 'ipv4_mapped') and ip.ipv4_mapped is not None:
|
||||
return ip.ipv4_mapped.is_loopback
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def _is_secure_context(handler=None) -> bool:
|
||||
"""Return True if cookies should carry the Secure flag.
|
||||
|
||||
Behaviour is overridable via HERMES_WEBUI_SECURE env var for
|
||||
reverse-proxy setups where TLS terminates at a frontend proxy
|
||||
(nginx, Cloudflare, etc.) and Python only sees plain HTTP.
|
||||
1/true/yes → force Secure on; 0/false/no → force Secure off.
|
||||
When unset, fall back to heuristics: direct TLS socket (getpeercert)
|
||||
or X-Forwarded-Proto header from the request.
|
||||
Priority order:
|
||||
1. ``HERMES_WEBUI_SECURE`` env var: 1/true/yes -> True; 0/false/no -> False.
|
||||
2. Direct TLS socket (handler.request.getpeercert present) -> True.
|
||||
3. ``HERMES_WEBUI_TRUST_FORWARDED_PROTO=1`` opt-in: trust
|
||||
``X-Forwarded-Proto: https`` header from a known reverse proxy.
|
||||
4. Otherwise -> False (loopback or non-loopback, plain HTTP is not secure).
|
||||
|
||||
.. warning::
|
||||
The ``X-Forwarded-Proto`` header is only trustworthy when a
|
||||
reverse proxy (nginx, Cloudflare, etc.) is deployed in front
|
||||
of the application. Without a proxy, any client can forge the
|
||||
header and cause the Secure flag to be set on plain HTTP.
|
||||
``X-Forwarded-Proto`` is only trustworthy behind a reverse proxy.
|
||||
It is ignored unless ``HERMES_WEBUI_TRUST_FORWARDED_PROTO=1`` is
|
||||
set explicitly, preventing header-injection attacks on plain-HTTP
|
||||
deployments.
|
||||
"""
|
||||
env = os.getenv('HERMES_WEBUI_SECURE', '').strip().lower()
|
||||
if env in ('1', 'true', 'yes'):
|
||||
@@ -564,8 +579,10 @@ def _is_secure_context(handler=None) -> bool:
|
||||
if handler is not None:
|
||||
if getattr(handler.request, 'getpeercert', None) is not None:
|
||||
return True
|
||||
if handler.headers.get('X-Forwarded-Proto', '') == 'https':
|
||||
return True
|
||||
trust_fwd = os.getenv('HERMES_WEBUI_TRUST_FORWARDED_PROTO', '').strip().lower()
|
||||
if trust_fwd in ('1', 'true', 'yes'):
|
||||
if handler.headers.get('X-Forwarded-Proto', '') == 'https':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user