{"record":{"id":"ba0a73d27d0e2a5c","repo":"microsoft/semantic-kernel","slug":"the-operation-path-resolves-to-requestpath-wh","errorCode":null,"errorMessage":"The operation path resolves to '{requestPath}', which is outside the configured server base path '{basePath}'.","messagePattern":"The operation path resolves to '(.+?)', which is outside the configured server base path '(.+?)'\\.","errorType":"exception","errorClass":"KernelException","httpStatus":null,"severity":"critical","filePath":"dotnet/src/Functions/Functions.OpenApi/Model/RestApiOperation.cs","lineNumber":205,"sourceCode":"    /// <param name=\"requestUrl\">The request URL produced by combining the server URL and operation path.</param>\n    private static void EnsureRequestTargetMatchesServer(Uri serverUrl, Uri requestUrl)\n    {\n        var serverAuthority = serverUrl.GetLeftPart(UriPartial.Authority);\n        var requestAuthority = requestUrl.GetLeftPart(UriPartial.Authority);\n\n        if (!string.Equals(serverAuthority, requestAuthority, StringComparison.OrdinalIgnoreCase))\n        {\n            throw new KernelException($\"The operation path resolves to '{requestAuthority}', which does not match the configured server '{serverAuthority}'.\");\n        }\n\n        // GetServerUrl guarantees a trailing slash, so the server's base path always ends with '/'.\n        var basePath = serverUrl.AbsolutePath;\n        var requestPath = requestUrl.AbsolutePath;\n\n        if (!string.Equals(requestPath, basePath.TrimEnd('/'), StringComparison.Ordinal) &&\n            !requestPath.StartsWith(basePath, StringComparison.Ordinal))\n        {\n            throw new KernelException($\"The operation path resolves to '{requestPath}', which is outside the configured server base path '{basePath}'.\");\n        }\n    }\n\n    /// <summary>\n    /// Builds operation request headers.\n    /// </summary>\n    /// <param name=\"arguments\">The operation arguments.</param>\n    /// <returns>The request headers.</returns>\n    internal IDictionary<string, string> BuildHeaders(IDictionary<string, object?> arguments)\n    {\n        var headers = new Dictionary<string, string>();\n\n        var parameters = this.Parameters.Where(p => p.Location == RestApiParameterLocation.Header);\n\n        foreach (var parameter in parameters)\n        {\n            var argument = this.GetArgumentForParameter(arguments, parameter);\n            if (argument == null)","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/microsoft/semantic-kernel/blob/c028a0c7dc4f0814cdcbaba9d998f187a41197bf/dotnet/src/Functions/Functions.OpenApi/Model/RestApiOperation.cs#L187-L223","documentation":"Companion SSRF guard in EnsureRequestTargetMatchesServer. After the authority check passes, the absolute path of the resolved request URL is compared to the server's base path. If the request path neither equals the base path (minus trailing slash) nor starts with it, the operation path has escaped the server's base path, and the request is rejected.","triggerScenarios":"A server URL with a base path (e.g. 'https://api.example.com/v1/') combined with an operation path that resolves outside /v1/ - e.g. a path of /../v2/resource that, after the dot-segment is canonicalized, lands in a sibling base path. Also an absolute path like /other/resource that ignores the server's /v1/ prefix.","commonSituations":"Spec authored with paths that ignore the server base path; a path-traversal-style template; merging specs with different base-path conventions.","solutions":["Align operation paths with the server's declared base path (paths should be relative and sit under the server's prefix).","Make sure no path contains traversal sequences; note ValidatePathSegments separately rejects '..' but this guard catches residual escapes after canonicalization.","If a different base path is genuinely needed, add it as an additional server entry rather than escaping the first one.","Audit the spec's servers[].url and every path to confirm prefix containment."],"exampleFix":"// before - server has a base path but the operation path escapes it\n\"servers\": [ { \"url\": \"https://api.example.com/v1/\" } ],\n\"paths\": { \"/v2/items\": { } }\n\n// after - operation path lives under the base path\n\"servers\": [ { \"url\": \"https://api.example.com/v1/\" } ],\n\"paths\": { \"/items\": { } }","handlingStrategy":"validation","validationCode":"var basePath = serverUrl.AbsolutePath;\nforeach (var pathKey in doc.Paths.Keys)\n{\n    var resolved = new Uri(serverUrl, pathKey.TrimStart('/')).AbsolutePath;\n    if (!resolved.Equals(basePath.TrimEnd('/'), StringComparison.Ordinal) && !resolved.StartsWith(basePath, StringComparison.Ordinal))\n        throw new InvalidOperationException($\"Path '{pathKey}' escapes server base path '{basePath}'.\");\n}","typeGuard":"static bool PathWithinBase(Uri serverUrl, string opPath)\n{\n    var basePath = serverUrl.AbsolutePath;\n    var resolved = new Uri(serverUrl, opPath.TrimStart('/')).AbsolutePath;\n    return resolved.Equals(basePath.TrimEnd('/'), StringComparison.Ordinal) || resolved.StartsWith(basePath, StringComparison.Ordinal);\n}","tryCatchPattern":"try { var url = operation.BuildOperationUrl(arguments); }\ncatch (KernelException ex) when (ex.Message.Contains(\"outside the configured server base path\"))\n{ logger.LogWarning(\"Operation path escapes server base path; fix the spec.\"); throw; }","preventionTips":["Align operation paths under the server's base path prefix.","Reject specs whose paths ignore the declared base path.","Treat the guard as a security control, not noise."],"tags":["openapi","ssrf","rest-operation","security","base-path"],"backgroundTag":null,"analyzedSha":"c028a0c7dc4f0814cdcbaba9d998f187a41197bf","analyzedAt":"2026-08-13T13:48:05.040Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}