iflytek/astron-agent · error · CallThirdApiException

ErrCode.SERVER_VALIDATE_ERR

ErrCode.SERVER_VALIDATE_ERR

Error message

<dynamic outbound policy violation: str(exc)>

What it means

_validate_destination runs the fully-built request URL through the OutboundPolicy (SSRF guard) before any connection is made. If the policy rejects the URL (malformed URL, non-HTTP scheme, blocked domain, blocked/unsafe IP, embedded userinfo, etc.), the OutboundPolicyError is wrapped in CallThirdApiException with code ErrCode.SERVER_VALIDATE_ERR and the policy message as err. This is the egress-security check that fails the tool call closed.

Solutions

  1. Read the err field for the underlying OutboundPolicyError message to see which rule fired
  2. If the destination is legitimately required, update the link-service env config: remove it from DOMAIN_BLACK_LIST/SEGMENT_BLACK_LIST/IP_BLACK_LIST or add it to PRIVATE_ENDPOINT_ALLOW_LIST / IP_WHITE_LIST as appropriate
  3. Fix the tool endpoint configuration (scheme, port, remove embedded credentials) so the URL passes the policy
Defensive patterns

Strategy: try-catch

Validate before calling

from urllib.parse import urlsplit
def passes_basic_policy(url, blocked_domains):
    p = urlsplit(url)
    host = (p.hostname or "").lower().rstrip(".")
    return p.scheme in ("http", "https") and not any(host == d or host.endswith('.' + d) for d in blocked_domains) and p.username is None and not p.fragment

Try / catch

try:
    result = await run.do_call(span)
except CallThirdApiException as e:
    if e.code == ErrCode.SERVER_VALIDATE_ERR.code:
        audit.log('egress blocked', url, e.err)
        raise PolicyViolation(e.err) from e
    raise

Prevention

When it happens

Trigger: Calling HttpRun.do_call (via _execute_request) where the final URL violates outbound policy: e.g. DOMAIN_BLACK_LIST match, IP segment/IP blacklist match, non-http(s) scheme, missing hostname, username/password in URL, fragment present, invalid port, or control characters.

Common situations: Tool registered against a hostname that ops has blacklisted; tool server uses an internal/private IP that is not on PRIVATE_ENDPOINT_ALLOW_LIST; URL contains 'user:pass@host' credentials; port typo like :99999.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/a97bacc80e54a340. Report an issue: GitHub.

Appendix: source

Thrown at core/plugin/link/infra/tool_exector/process.py:101

            self._is_auth_hmac = False
            self.auth_con_js = object
        try:
            self._is_official = HttpRun.is_official(open_api_schema)
        except Exception:
            self._is_official = False
        # Invalid security configuration must fail closed instead of silently disabling checks.
        self._outbound_policy = OutboundPolicy.from_environment()

    def _validate_destination(self, url: str) -> None:
        """Validate the final URL before constructing an outbound connection.

        Raises:
            CallThirdApiException: When the destination violates egress policy
        """
        try:
            self._outbound_policy.validate_url(url)
        except OutboundPolicyError as exc:
            raise CallThirdApiException(
                code=ErrCode.SERVER_VALIDATE_ERR.code,
                err_pre=ErrCode.SERVER_VALIDATE_ERR.msg,
                err=str(exc),
            ) from exc

    def _build_url(self) -> str:
        """Build request URL with authentication and query parameters.

        Returns:
            str: Complete URL for the request
        """
        url = self.server

        # Substitute OpenAPI path parameters as individual path segments. urljoin is unsafe here:
        # an absolute value, a scheme-relative value, or a dot segment can replace/escape the
        # persisted endpoint path.
        for name, value in self.path.items():
            placeholder = "{" + str(name) + "}"

View on GitHub (pinned to 5e758547a8)