Skip to content

[security] fix(action): block unsafe OpenAPI action URLs#106

Open
Hinotoi-agent wants to merge 1 commit into
MLT-OSS:mainfrom
Hinotoi-agent:fix/secure-openapi-action-egress
Open

[security] fix(action): block unsafe OpenAPI action URLs#106
Hinotoi-agent wants to merge 1 commit into
MLT-OSS:mainfrom
Hinotoi-agent:fix/secure-openapi-action-egress

Conversation

@Hinotoi-agent
Copy link
Copy Markdown

Summary

This PR hardens OpenAPI-backed actions against server-side request forgery (SSRF). Action schemas currently accept an arbitrary servers[0].url, store the derived action URL, and later pass it directly to requests.request() when the action is run.

The patch adds action URL validation at both important layers:

  • when an OpenAPI schema is created or updated
  • immediately before the outbound action request is sent, including every redirect hop

Security issues covered

Issue Severity Status
OpenAPI action server URL can target localhost, private networks, link-local/cloud metadata, or other internal destinations High Fixed
Redirects from an apparently external action endpoint can pivot to blocked internal destinations High Fixed
Action HTTP calls had no explicit timeout Defense-in-depth Fixed

Before this PR

  • validate_openapi_schema() checked OpenAPI structure, path metadata, and operation IDs, but not whether servers[0].url was safe for server-side fetching.
  • extract_params() built the action URL from servers[0].url and the path.
  • call_action_api() sent the URL to requests.request() with default redirect behavior and no private-address denylist.
  • A caller able to create and run an action could cause the API server to make requests to loopback, private network ranges, link-local metadata services, or internal DNS names and receive the response body/status back through the action result.

After this PR

  • Action server URLs are limited to http and https.
  • URLs with user credentials are rejected.
  • localhost, .localhost, loopback, private, link-local, multicast, reserved, and unspecified destinations are rejected.
  • DNS names are resolved and rejected if any resolved address falls into the blocked ranges.
  • Action execution revalidates the final URL before the request is sent.
  • Redirects are handled manually with allow_redirects=False; each Location target is resolved and revalidated before another request is made.
  • Action HTTP calls now use an explicit timeout, configurable through ACTION_HTTP_TIMEOUT and defaulting to 10 seconds.

Why this matters

OpenAPI actions are a server-side network primitive. If untrusted users can create or run actions, an attacker can use the API server's network position to reach services that are not reachable from the public Internet, including loopback-only admin endpoints, container/service-network hosts, and cloud metadata endpoints.

Because the action response is returned to the caller, this is not only blind SSRF; it can disclose internal HTTP responses and can interact with internal endpoints using multiple HTTP methods.

Attack flow

  1. An attacker submits an OpenAPI action schema whose servers[0].url points at an internal destination, such as http://127.0.0.1:8080 or a link-local metadata endpoint.
  2. The action builder combines that server URL with the OpenAPI path and stores the action URL.
  3. The attacker runs the action.
  4. The API server performs the outbound HTTP request from its own network context.
  5. The response status and body are returned in the action result.

A redirect variant is also possible without this patch:

  1. The action server URL initially points at an external host.
  2. The external host returns a redirect to an internal address.
  3. The default redirect-following behavior sends the server-side request to the internal target.

Affected code

  • app/schemas/tool/action.py
    • OpenAPI schema validation for action creation/update.
  • app/services/tool/openapi_call.py
    • Runtime action HTTP execution sink.
  • app/services/tool/openapi_utils.py
    • Existing URL construction path that combines servers[0].url and the OpenAPI path.

Root cause

  • The OpenAPI action server URL was treated as trusted configuration after basic schema validation.
  • The code did not distinguish normal external action endpoints from internal or local network destinations.
  • Runtime request execution relied on requests default redirect handling, so redirect destinations were not revalidated before the next request.

CVSS assessment

  • Issue: OpenAPI action SSRF with response disclosure
  • CVSS v3.1: 8.8 High
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L
  • Rationale: in deployments where the action API is reachable by untrusted callers, the attack is network-reachable, low complexity, requires no user interaction, and can disclose or interact with internal HTTP services from the API server/container network context. Availability impact is lower but present because outbound requests previously had no explicit timeout.

Safe reproduction steps

On the vulnerable code path, a narrow harness can demonstrate the issue without contacting real internal services:

  1. Build a valid OpenAPI action schema with servers[0].url set to http://127.0.0.1:59999 and a path such as /secret.
  2. Run the existing action URL extraction path.
  3. Monkeypatch requests.request and call call_action_api() with the extracted URL.
  4. Observe that the sink receives http://127.0.0.1:59999/secret and returns the mocked response body to the caller.

This PR adds regression coverage for the blocked behavior instead of relying on a live internal service.

Expected vulnerable behavior

Before this PR, the action execution sink accepts an action URL targeting loopback/private destinations or follows redirects to those destinations, then returns the upstream response status/body to the action caller.

Changes in this PR

  • Adds app/services/tool/url_security.py for action egress URL validation.
  • Validates OpenAPI action servers[0].url before accepting the schema.
  • Revalidates the runtime action URL immediately before outbound network I/O.
  • Disables automatic redirects and manually revalidates each redirect target.
  • Adds a bounded redirect limit.
  • Adds an explicit action request timeout.
  • Adds focused unit tests for loopback, DNS-to-private-address, metadata endpoint, and redirect-to-loopback cases.

Files changed

Category Files What changed
URL safety helper app/services/tool/url_security.py New validation helper for schemes, credentials, hostnames, IP ranges, and DNS resolution
OpenAPI validation app/schemas/tool/action.py Rejects unsafe action server URLs during schema validation
Runtime request sink app/services/tool/openapi_call.py Revalidates action URLs, disables automatic redirects, validates redirect targets, and adds timeout
Tests tests/unit/test_action_url_security.py Adds SSRF regression coverage

Maintainer impact

  • Public action integrations over normal http and https hosts continue to work.
  • Action schemas that point at loopback, private networks, link-local addresses, metadata endpoints, or DNS names resolving to blocked addresses are now rejected.
  • Redirects to blocked destinations are now stopped instead of followed.
  • Deployments that intentionally need private-network action targets should add an explicit allowlist or trusted-operator egress policy rather than accepting arbitrary user-supplied action URLs.

Fix rationale

The safest boundary is the outbound action request itself. Validating only when the schema is submitted is not enough because runtime redirects can change the destination. This PR therefore validates both at persistence time and at the actual network sink, with redirect targets checked before the next request is sent.

Type of change

  • Security fix
  • Bug fix
  • Tests
  • Documentation-only change
  • Breaking API change

Test plan

Commands run locally:

  • python3 -m pytest tests/unit/test_action_url_security.py -q
  • python3 -m py_compile app/services/tool/url_security.py app/services/tool/openapi_call.py app/schemas/tool/action.py tests/unit/test_action_url_security.py
  • git diff --check
  • python3 -m ruff check app/services/tool/url_security.py tests/unit/test_action_url_security.py
  • python3 -m ruff format --check app/services/tool/url_security.py app/services/tool/openapi_call.py app/schemas/tool/action.py tests/unit/test_action_url_security.py

Notes:

  • python3 -m ruff check app/schemas/tool/action.py still reports pre-existing issues unrelated to this patch: two N805 validator naming findings and one F811 duplicate ActionRunRequest definition. The new helper and new test file pass Ruff, and the touched files pass Ruff format check.
  • python3 -m black --check ... could not be run in this local environment because Black is not installed and Poetry is not available on PATH here.

Disclosure notes

This PR is intentionally scoped to OpenAPI action egress hardening. It does not claim to redesign action authorization or define a deployment-specific allowlist policy. The security impact is highest when action creation or action execution is reachable by untrusted users; if a deployment restricts action management to trusted operators only, this remains useful defense-in-depth against accidental internal egress exposure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant