GitHub-Flavored Markdown ≠ Telegram MarkdownV2, so the old plain-text send
showed literal ## ** | etc. New .github/scripts/tg-notify.mjs converts GFM
→ Telegram-safe HTML (<b>/<i>/<code>/<pre>/<a>; headers→bold, tables→· rows,
lists→•, quotes→▎), escapes <>&, and falls back to plain text if Telegram
rejects the HTML. Adds workflow_dispatch to telegram-notify so you can send
the latest release rendered for testing without cutting a new release.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New .github/workflows/telegram-notify.yml: on release published or issue
opened, send a message to a Telegram group via @WechatOnCloudBot. Runs on
GitHub Actions (no server). Gated on vars.TELEGRAM_CHAT_ID so unconfigured
forks skip safely. Arbitrary text passed via env (no script injection),
sent as plain text with --data-urlencode (no markdown parse breakage).
Setup documented in doc/发布到GHCR.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All instances shared the image-baked machine-id (a67bf09f...), so Tencent
saw every WechatOnCloud account worldwide as one "device" — a textbook
device-farm signal triggering risk control and the forced-logout loop
reported across old and new versions.
- docker/woc-identity.sh: new /custom-cont-init.d/00-woc-identity hook —
generates a unique machine-id on first start, persists it in the data
volume (survives restart/upgrade/recreate), writes /etc/machine-id +
/var/lib/dbus/machine-id, removes /.dockerenv. Existing instances get a
fresh unique id on first upgraded start (volume lacks the file).
- regenInstanceMachineId + POST /api/admin/instances/:id/regen-machine-id:
roll a brand-new device id and restart, for accounts re-flagged by risk
control. Gated on the hook being present (old image → instructs upgrade).
- Admin 实例卡片「安全」弹窗新增「重置设备 ID 并重启」。
Verified: two fresh containers get distinct machine-ids; id persists across
restart; regen (rm persisted file + restart) yields a new persistent id.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- release.yml: dual-push to GHCR + Docker Hub when vars.DOCKERHUB_USERNAME
is set; falls back to GHCR-only when unset (no behavior change for forks).
- .env.example: surface docker.io as a first-class WOC_IMAGE_PREFIX option.
- doc/发布到GHCR.md: document the one-time Variable + Secret setup and the
prerequisite of pre-creating the public repos on hub.docker.com.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- .env.example: surface multi-domain syntax (PANEL_ALLOWED_HOSTS=a,b,c),
IPv6 literal example, and reverse-proxy troubleshooting tip.
- index.ts: 400 response includes the rejected `host` and a hint pointing
at PANEL_ALLOWED_HOSTS — drops the diagnostic floor when reverse-proxy
Host-passthrough is misconfigured.
Follow-up to #13 (DNS-rebinding allowlist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without Host validation, a malicious page the operator visits can use DNS
rebinding to point a hostname at the panel's loopback / LAN IP and drive
every authenticated API from the operator's own browser — including the
docker.sock-backed admin endpoints. The README's "intranet-only" guidance
does not cover this: the browser is the trust-boundary crossing.
Add an onRequest hook (plus a Host check on raw WebSocket upgrades) that
allows loopback + RFC1918 LAN by default and accepts public hostnames via
PANEL_ALLOWED_HOSTS (documented in .env.example and threaded through
docker-compose.yml). 35 inject()-driven assertions; tsc --noEmit clean.
Detected by Aeon + manual review (DNS-rebinding-gate axis).
Severity: high
CWE-346 (Origin Validation Error)