* fix(adapters): stop SSRF and arbitrary local file reads in media handling
The platform adapters fetch URLs and open paths that come from inbound
messages, so both are attacker-influenced.
New guard module pkg/utils/outbound_url.py:
- assert_safe_outbound_url: checks the scheme, rejects literal private /
loopback / link-local / reserved / multicast addresses, and resolves the
hostname so a public-looking name pointing at an internal address is refused
too. Optional allowed_domains confines a fetch to a known prefix.
- assert_safe_local_media_path: resolves a caller-supplied path and requires it
to stay inside the media roots (data dir + temp dir), so symlinks and .. cannot
escape. Directories are refused.
Wired in:
- DingTalk send_message: session_webhook is taken from the inbound event and was
POSTed verbatim, so a forged event could redirect reply content (including LLM
output) to any host. Now restricted to dingtalk.com.
- DingTalk send_attachment: component.url was fetched with no destination check;
now goes through the SSRF guard, which also covers cloud metadata.
- DingTalk upload_image_media: caller-supplied file_path was opened directly; now
confined to the media roots.
- Slack attachment_bytes: component.path was opened directly and uploaded to the
channel (arbitrary file exfiltration); now confined to the media roots.
- Slack download_media: redirects were followed with no destination check. Every
hop is now validated, the hop count is bounded, and a redirect off the Slack
domain drops the bearer token.
Tests: 25 new cases for the guard (internal address forms, scheme rejection,
suffix impostor, traversal, symlink escape, directory). The Slack credential test
now stubs DNS because the guard resolves before fetching, and asserts
allow_redirects is False for every case since redirects are handled manually.
Co-authored-by: multica-agent <[email protected]>
* fix(web): hide passkey UI unless the core reports it is supported
The core refuses every passkey route on a cloud deployment (403
edition_unsupported) and reports passkey_login_enabled/passkey_supported as
false. The WebUI still rendered the controls:
- Login page read 'passkey_login_enabled !== false || passkey_supported', so an
absent flag counted as enabled. It now requires an explicit true.
- The account settings panel always rendered the passkey section and called
GET /passkeys on open, which 403s on cloud. It now reads the capabilities
first, skips the list call when unsupported, and does not render the section.
Also drop the client-supplied origin from both passkey options requests. The
server resolves the WebAuthn RP ID and expected origin from api.webui_url /
api.webhook_prefix, so sending window.location.origin was dead weight that would
invite a future handler to trust it again.
Verified: tsc --noEmit clean, eslint clean (only two pre-existing hook-dep
warnings), pnpm build succeeds.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
Cloud deployments need a way to tell readers what is happening to the
instance they are using (maintenance windows, service notices) without
shipping a release per notice.
Add a cloud.banner config block (switch, tone, dismissal, link targets and
per-language copy) and expose the resolved banner on the unauthenticated
/api/v1/system/info payload so the WebUI can render it before login. The
banner resolves to null on OSS deployments, so a self-hosted copy of
config.yaml never shows managed-service copy. Copy is plain text; only
absolute http(s) link targets are rendered.
The WebUI renders it once from the root layout, offsets the fixed app shell
and the wizard through --cloud-banner-height, and keeps a dismissal bound to
the notice it dismissed so new copy reappears.
Co-authored-by: multica-agent <[email protected]>
- creditsLabel: 每月赠送积分 → 模型积分 (zh), Monthly credits → Model credits (en), 毎月のクレジット → モデルクレジット (ja)
- creditsHelp: append note that own model providers remain usable after credits run out, all locales
- WorkspaceSwitcher trigger: cap workspace name to 6rem, remove justify-start
- HomeSidebar: add ArrowUp upgrade icon button next to workspace switcher (cloud_projection only), opens CloudPlansDialog lazy-loaded
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Set no-referrer on message thumbnails and previews to prevent
QQ image requests from returning 400 and triggering ORB.
Add browser coverage for image loading and request headers.
Apply group response rules before busy-session steering. Initialize usable private-message and group-mention routes for new bots, including Wizard-created pipelines, and preserve member-configured rules across saves and retries.
Fix empty pipeline configuration, refresh the processor sidebar after automatic creation, and show creation feedback. Align routing regressions and use Node 24 for the browser CI job.
Correct the task's erroneous agent attribution while preserving every code tree and the original human contribution.
Co-authored-by: multica-agent <[email protected]>
Preserve focus when another control becomes active during the close animation.
Add regression tests for filter persistence, Escape, and custom range dialog focus.
- Load overview first and fetch secondary sections when expanded
- Add a covering index and select conversation IDs before loading page payloads
- Preserve full-detail API compatibility and add pagination and lazy-loading tests
- Use LangBot Account wording in the Japanese checkout handoff copy.
- Assert the Workspace Settings plan entry opens CloudPlansDialog.
- Format revision tests with Prettier.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Expose the workspace's purchased plan revision from the entitlement
snapshot.
- Stack the purchased revision card in front of the latest revision in
the plan comparison dialog; both cards share the same layout, and the
purchased card's copy and prices come from the Space revision endpoint
while benefits stay on the entitlement snapshot.
- Add a cancel-first tooltip for subscribing to the newer revision.
Co-Authored-By: Claude Opus 5.5 <[email protected]>