Confirmed with the product team that the official GUI client applies no
client-side SSRF interception to downloads, so the IP-literal refusal
was the last remaining client-side interception beyond transport
hygiene. Dedicated deployments make every host dimension (domain,
port, network location) unenumerable, and an IP-literal host is just
another network location.
- ValidateDownloadURL accepts any HTTPS host, IP literals included;
HTTPS scheme, userinfo refusal, and per-hop redirect re-validation
stay as the transport baseline
- this retires the third and last interception layer after the host
allowlist removal and the dial-time public-IP refusal (be52ca5d)
- upload targets stay unaffected: trustedUploadHost keeps rejecting
non-DingTalk/OSS hosts, so local file bytes cannot be PUT to an IP
- regressions: IP-literal URLs pass validation on the shared and chat
download paths; userinfo and plain-HTTP URLs stay rejected
Customer round-2 testing on the dedicated deployment (Jingbo) found the
storage domain resolving to a customer-intranet address (10.254.87.52),
which the dial-time public-IP policy refused: dedicated storage can be
deployed inside the customer network, so its resolved network location
is as unenumerable as its domain and port.
- delete the public-IP policy, the IANA special-purpose denylist, and
the NAT64 embedded-IPv4 re-validation introduced in 617b780a;
downloads now dial the service-issued host directly, still ignoring
environment proxies
- align with the official GUI client, which applies no client-side
SSRF interception to downloads: no command accepts a user-supplied
download URL, TLS hostname verification pins the connection to the
requested domain, redirects are re-validated per hop, and credential
headers are stripped once a redirect leaves the original origin
- uploads keep the static DingTalk/OSS default-port trust boundary
- simplify SetSecureDownloadDialTargetForTest to a single dial seam
Customer testing on a dedicated deployment found real download URLs served
on a non-default HTTPS port (e.g. 8443) by the dedicated storage domain,
which the inherited default-port-only rule rejected.
- drop the 443-only restriction from ValidateDownloadURL; HTTPS scheme,
domain-only hosts (no IP literals), and no-userinfo rules stay
- the port is not a trust signal: SSRF protection lives at dial time in
the port-agnostic public-IP policy
- redirect hygiene unchanged: a port change is a cross-origin redirect
and still strips service credential headers (new regression guard)
- dedicated-deployment regression: same host on a non-default port is
accepted by URL validation and downloads successfully
- restore the pre-existing DingTalk/OSS trusted host requirement for
upload target URLs via a dedicated upload validator
- extend the dial-time non-public IP denylist with IANA special-purpose
ranges (0.0.0.0/8, 192.88.99.0/24, 100::/64, 2002::/16, 3fff::/20,
5f00::/16)
- document that download credential headers follow the service-issued
URL as-is on the first hop (same authenticated response issues both);
cross-host redirects keep stripping them
- schemaPublishedShortcutCount: 461→468 after merging aisearch/contact/live
shortcuts from upstream/main
- Fix P2: datasource-update --field-ids doc says "不传时同步全部字段" but
actual behavior keeps existing field config; fixed in usage guide and
reference
- Add trimNonEmpty for --table-ids in +datasource-sync and --task-ids in
+datasource-sync-status, matching the existing field-ids pattern
- Add 4 test cases: whitespace-only rejection and trim-through for both
- Fix usage guide: typical workflow and notes no longer equate result
with processCode; correctly describe result as JSON to parse for
approvals[].processCode/name/iconUrl/url
--field-ids is declared as FlagStringSlice, but DatasourceCreate and
DatasourceUpdate previously called rt.Str to check whether the flag
was empty. RuntimeContext.Str delegates to cobra's GetString, which
returns an empty string on slice-typed flags, so the empty-value
guard rejected every explicit --field-ids input and the downstream
MCP tool never received fieldIds.
Switch to rt.StrSlice, sanitize through a new trimNonEmpty helper
(drop whitespace-only / empty entries) and pass the cleaned slice
to MCP. Add success-passthrough tests for both create and update,
plus a whitespace-only rejection case, and enhance the mock caller
to record MCP arguments so fieldIds can be asserted.
- Update --field-ids description in create/update shortcuts and the
helper-layer datasource update to clarify that omitting the flag
keeps existing config (create defaults to all fields), matching the
actual update overwrite semantics.
- Rename datasource shortcut coverage tests to the
TestCrossPlatformCoverage* prefix so they are picked up by the
macOS platform coverage gate.
Align shortcut layer validation with helper layer to prevent empty slices
from being sent to MCP, which could clear sync field selection due to
datasource update's overwrite semantics.
- Add empty string checks for --field-ids in both create and update shortcuts
- Add empty string checks for --auto-sync-setting in both create and update shortcuts
- Add regression tests verifying MCP is not called when empty values are rejected
- Both public entry points now have consistent validation behavior
Fixes P1 auto-CR issue for empty flag bypass vulnerability.
Make +datasource-sync-status consistent across shortcut and native
commands: --task-ids is now required, descriptions focus on querying
by taskId, and optional/IDLE semantics are removed. Update tests,
usage guide, reference doc, and SKILL description accordingly.