conductor-oss / conductor-oss/conductor

Security: SSRF protection needed in HTTP task

Open
#1,018 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
32.2k
Forks
1k
Avg merge
2d 5h
Merged PRs (30d)
41

Description

## Summary

The `HTTP_TASK` system task passes user-controlled URIs directly to `restTemplate.exchange()` with no restrictions on what hosts can be reached. This enables Server-Side Request Forgery (SSRF): anyone who can define or modify a workflow can instruct the Conductor server to make HTTP requests to internal infrastructure.

**CodeQL Alert #2 · `http-task/.../HttpTask.java:175` · severity: error**

## The Problem

```java
// HttpTask.java:174-178
ResponseEntity responseEntity =
restTemplate.exchange(
input.getUri(), // ← fully user-controlled, no validation
HttpMethod.valueOf(input.getMethod()),
request,
String.class);
```

There is no validation of `input.getUri()` before the call. An attacker with access to workflow definition APIs can target:

- **Cloud metadata endpoints** — `http://169.254.169.254/latest/meta-data/` (AWS IMDSv1, GCP, Azure) to steal instance credentials
- **RFC 1918 internal services** — `http://10.x.x.x/`, `http://192.168.x.x/`, `http://172.16-31.x.x/`
- **Loopback** — `http://localhost:8080/api/admin/...` (Conductor's own admin API)
- **Internal DNS names** — `http://internal-db-host:5432/`

## Why This Is By-Design — and Still a Problem

The HTTP task is *intentionally* designed to call external endpoints — that's its entire purpose. The issue is the absence of any configurable guard rails. Most production deployments need to reach external services, but should not allow the Conductor server itself to become a proxy into its own internal network.

## Proposed Solution

Add a configurable `HttpTaskProperties` class (or extend existing config) with:

1. **Block-by-default list**: RFC 1918 ranges, loopback (`127.0.0.0/8`), link-local (`169.254.0.0/16`), and `::1`
2. **Allowlist** (`conductor.system-task.http.allowed-hosts`): explicit patterns that override the block list for legitimate internal calls
3. **Denylist** (`conductor.system-task.http.denied-hosts`): additional patterns to block beyond defaults
4. URI validation at the start of `httpCall()`, before `restTemplate.exchange()` is called

Example config:
```yaml
conductor:
system-task:
http:
deny-internal-network: true # default: true
allowed-hosts:
- "api.trusted-partner.com"
denied-hosts:
- "*.internal.corp"
```

## Threat Model Note

Risk is higher in multi-tenant or externally-accessible Conductor deployments where untrusted parties can submit workflow definitions. In fully internal, trusted-user-only deployments the practical risk is lower, but the fix is still correct defense-in-depth.

## References

- [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)
- CodeQL alert: https://github.com/conductor-oss/conductor/security/code-scanning/2
- Epic: #1010

Contributor guide

Open the contributing guide

Research direction

Start in HttpTask.java around lines 174-178 and the httpCall() entry point, then review how existing system-task configuration is represented. Trace input.getUri() through to restTemplate.exchange() and define how the proposed default blocking, allowlist, and denylist settings should be applied before the request; done means the CodeQL SSRF path is guarded without breaking intended external calls.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring-boot
Domain
backend, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.