{"record":{"id":"63a25b5790a9c619","repo":"iflytek/astron-agent","slug":"model-url-check-failed","errorCode":"MODEL_URL_CHECK_FAILED","errorMessage":"BusinessException(ResponseEnum.MODEL_URL_CHECK_FAILED)","messagePattern":"BusinessException\\(ResponseEnum\\.MODEL_URL_CHECK_FAILED\\)","errorType":"error_code","errorClass":"BusinessException","httpStatus":null,"severity":"error","filePath":"console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/service/model/ModelService.java","lineNumber":268,"sourceCode":"     * path; dual validation for entry and final URL.\n     */\n    private String buildModelApiUrlNew(String baseUrl, String provider, String modelDomain) {\n        try {\n            List<String> ipBlacklist = loadIpRules(CAT_IP_BLACKLIST);\n            List<String> ipWhitelist = loadIpRules(CAT_IP_WHITELIST);\n            SsrfProperties ssrfProperties = new SsrfProperties();\n            // Note: The underlying object field name is ipBlaklist (third-party spelling), maintain\n            // compatibility\n            ssrfProperties.setIpBlaklist(ipBlacklist);\n            ssrfProperties.setIpWhitelist(ipWhitelist);\n\n            // 0) Remove userInfo and normalize\n            String stripped = SsrfValidators.stripUserInfo(baseUrl);\n            URL normalized = SsrfValidators.normalize(stripped);\n\n            // 1) Prohibit query/fragment\n            if (normalized.getQuery() != null) {\n                throw new BusinessException(ResponseEnum.MODEL_URL_CHECK_FAILED);\n            }\n\n            SsrfParamGuard guard = new SsrfParamGuard(ssrfProperties);\n\n            // 2) Only do pre-validation on host segment\n            String hostOnly =\n                    normalized.getProtocol()\n                            + \"://\"\n                            + normalized.getHost()\n                            + (normalized.getPort() != -1 ? (\":\" + normalized.getPort()) : \"\");\n            guard.validateUrlParam(hostOnly);\n\n            // 3) Path completion\n            String path = Optional.ofNullable(normalized.getPath()).orElse(\"\");\n            String cleanedPath = path.replaceAll(\"/+$\", \"\");\n            String finalPath = completeApiPath(cleanedPath, provider, modelDomain);\n\n            // SECURITY FIX: Validate path to prevent directory traversal","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/service/model/ModelService.java#L250-L286","documentation":"MODEL_URL_CHECK_FAILED is thrown by buildModelApiUrlNew during SSRF-safe URL construction. After stripping userInfo and normalizing the base URL, any query string (?key=value) or fragment present is rejected outright, because query parts are used to smuggle credentials or bypass SSRF checks.","triggerScenarios":"Saving/validating a model whose endpoint baseUrl includes a query string (e.g. https://api.example.com/v1?x=1) or a fragment (#frag) — normalize() keeps the query and the guard at line ~268 throws.","commonSituations":"User pastes a full URL copied from a browser address bar that includes ?key=... or tracking parameters; provider docs show URLs with query params like ?api-version=2024-02-01 (Azure style).","solutions":["Remove the query string and fragment from the endpoint URL; keep only scheme://host/path.","If an API key was passed as a query parameter, configure it in the API key field instead of the URL.","For Azure-style api-version requirements, put the parameter in the provider's payload/config handling rather than the saved endpoint.","Re-save the model and re-run validation."],"exampleFix":"// before\nendpoint = \"https://generativelanguage.googleapis.com/v1beta/models?key=AIza...\";\n// after\nendpoint = \"https://generativelanguage.googleapis.com/v1beta/models\"; // key goes in apiKey field","handlingStrategy":"validation","validationCode":"java.net.URI u = java.net.URI.create(endpoint);\nif (u.getQuery() != null || u.getFragment() != null) throw new IllegalArgumentException(\"endpoint must not contain query or fragment: \" + endpoint);","typeGuard":"boolean isCleanUrl(String s) { try { java.net.URI u = java.net.URI.create(s); return u.getScheme() != null && u.getHost() != null && u.getQuery() == null && u.getFragment() == null; } catch (Exception e) { return false; } }","tryCatchPattern":"try { modelService.validateModel(req); } catch (BusinessException e) { if (\"MODEL_URL_CHECK_FAILED\".equals(e.getCode())) { promptUserToStripQueryAndFragment(); } throw e; }","preventionTips":["Copy only scheme://host/path into endpoint fields; put credentials/keys in dedicated fields.","Strip browser-address-bar query params before pasting URLs.","Handle provider-required query params (e.g. api-version) via provider config, not the saved URL."],"tags":["ssrf","url-validation","security"],"backgroundTag":"invalid-url-format","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}