Skip to content

fix: harden web API pipeline loading and request validation - #18

Merged
shouc merged 1 commit into
agentenv:masterfrom
Hinotoi-agent:fix/web-api-pipeline-path-guard
Apr 25, 2026
Merged

fix: harden web API pipeline loading and request validation#18
shouc merged 1 commit into
agentenv:masterfrom
Hinotoi-agent:fix/web-api-pipeline-path-guard

Conversation

@Hinotoi-agent

Copy link
Copy Markdown
Contributor

Summary

This PR hardens the local web UI/API control plane so browser-reachable requests cannot use the web API to execute arbitrary local pipeline files by path.

  • requires application/json on /api/runs and /api/runs/validate
  • disables pipeline_path on those web API endpoints by default
  • adds an explicit trusted-operator opt-in via AGENTFLOW_API_ALLOW_PIPELINE_PATH=1
  • adds focused regression coverage for the new default-deny behavior and opt-in path

Security issues covered

Issue Impact Severity
Web API accepted pipeline_path and loaded local .py pipeline files browser-reachable local control-plane request could trigger execution of a local Python pipeline file High
Web API accepted non-JSON content types for run creation/validation widened browser-driven attack surface for localhost control-plane abuse Medium

Before this PR

  • /api/runs and /api/runs/validate accepted requests that supplied pipeline_path
  • those routes could call load_pipeline_from_path(...), which executes .py pipeline files during loading
  • the web API did not enforce application/json on these routes
  • there was no regression coverage preventing future reintroduction of this path through the browser-facing API

After this PR

  • /api/runs and /api/runs/validate now reject non-JSON requests with HTTP 415
  • pipeline_path is disabled for the web API by default and rejected with HTTP 403
  • trusted local operators can re-enable path-based loading explicitly with AGENTFLOW_API_ALLOW_PIPELINE_PATH=1
  • tests now lock in default-deny behavior, the explicit opt-in path, and JSON-only request handling

Explicit operator choice

Secure default:

agentflow serve

Behavior:

  • binds locally as before
  • rejects non-JSON requests on /api/runs and /api/runs/validate
  • rejects pipeline_path on those browser-facing endpoints

Explicit trusted opt-in:

AGENTFLOW_API_ALLOW_PIPELINE_PATH=1 agentflow serve

Behavior:

  • preserves trusted local operator workflows that intentionally load pipelines from filesystem paths through the web API
  • keeps that capability behind an explicit environment-level decision instead of enabling it implicitly for every browser-reachable request

Why this matters

The web UI/API is a local control plane, not an untrusted code-loading surface. Before this patch, a request that could reach the browser-facing API could ask AgentFlow to load a local pipeline file by path, including a .py pipeline that executes during loading. Requiring JSON and default-disabling pipeline_path restores the intended trust boundary for the web API while keeping an explicit opt-in for trusted local workflows.

Attack flow

browser-reachable request with pipeline_path
    -> /api/runs or /api/runs/validate
        -> load_pipeline_from_path(...)
            -> local .py pipeline execution during load

Affected code

Issue Files
Default-disable pipeline_path for web API requests agentflow/app.py, tests/test_api.py
Enforce JSON-only request handling on web API routes agentflow/app.py, tests/test_api.py
Document secure default and explicit opt-in README.md, docs/cli.md

Root cause

Issue 1: web API path-based pipeline loading

  • the web API accepted a pipeline_path field and passed it into load_pipeline_from_path(...)
  • that trusted a browser-facing control-plane request with a filesystem-loading primitive that can execute local .py pipeline files

Issue 2: missing JSON content-type enforcement

  • the web API accepted non-JSON content types on run creation and validation routes
  • that unnecessarily broadened the set of request shapes a browser could use to reach the vulnerable control plane

CVSS assessment

Issue CVSS v3.1 Vector
Web API accepted pipeline_path and loaded local .py pipeline files 7.3 High CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Web API accepted non-JSON content types for run creation/validation 4.3 Medium CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N

Rationale:

  • the primary issue is a local/browser-reachable control-plane boundary break that can lead to unintended code execution from a local pipeline file path
  • the content-type issue is lower severity on its own, but materially weakens the web API boundary and helps browser-driven abuse of localhost surfaces

Safe reproduction steps

1. Default-denied pipeline_path on the web API

  1. Create a temporary local pipeline file.
  2. Send POST /api/runs/validate with JSON body { "pipeline_path": "/path/to/pipeline" }.
  3. Observe HTTP 403 with pipeline_path is disabled for the web API by default.

2. JSON-only enforcement

  1. Send POST /api/runs/validate with a non-JSON Content-Type such as text/plain.
  2. Use an otherwise valid JSON payload body.
  3. Observe HTTP 415 with application/json content type required.

3. Explicit trusted opt-in

  1. Start the server with AGENTFLOW_API_ALLOW_PIPELINE_PATH=1.
  2. Send POST /api/runs/validate with JSON body containing a valid local pipeline_path.
  3. Observe that the route accepts the path-based trusted local workflow again.

Expected vulnerable behavior

  • A browser-facing request should never have been able to drive local filesystem pipeline loading by path on the web API.
  • Before this patch, the web API accepted that request shape and did not require JSON-only handling on these routes.

Changes in this PR

  • add _require_json_request(...) and apply it to /api/runs and /api/runs/validate
  • add _api_pipeline_path_enabled() as an explicit opt-in gate
  • change _parse_pipeline_payload(...) so web API callers reject pipeline_path by default with HTTP 403
  • add focused regression tests for default-denied pipeline_path, explicit opt-in enablement, and non-JSON rejection
  • document the secure default and explicit opt-in behavior in README.md and docs/cli.md

Files changed

Category Files What changed
Runtime guardrails agentflow/app.py added JSON-only enforcement and default-denied pipeline_path gate for web API routes
Regression tests tests/test_api.py added coverage for 403 default deny, 415 non-JSON rejection, and explicit opt-in success
Documentation README.md, docs/cli.md documented secure default behavior and explicit trusted opt-in

Maintainer impact

  • patch scope is narrow and limited to the browser-facing web API entrypoint plus docs/tests
  • CLI and trusted local path-based workflows remain available through an explicit opt-in instead of silent default exposure
  • the new control point is centralized in agentflow/app.py, which should make future refactors easier to keep secure

Suggested fix rationale

  • the web API should treat filesystem path loading as privileged control-plane behavior, not as a default request capability
  • requiring JSON on these routes is a small, durable boundary check that reduces browser-driven localhost abuse paths
  • the opt-in model keeps legitimate trusted local workflows available without making the dangerous behavior the default
  • the regression tests cover the vulnerable edge directly and should make accidental reintroduction obvious

Reference patterns from other software

  • GitHub Actions requires explicit approval before running untrusted fork workflows; this PR follows the same secure-by-default principle for a privileged control-plane path
  • Kubernetes RBAC and Keycloak fine-grained admin permissions similarly separate ordinary runtime access from explicit privileged actions

Type of change

  • Security fix
  • Tests
  • Documentation update
  • Refactor with no behavior change

Test plan

  • python -m pytest tests/test_api.py -q
  • manually reviewed the route-to-loader code path in agentflow/app.py and agentflow/loader.py
  • full repository test suite

Executed with:

  • source /Users/lennon/.hermes/hermes-agent/venv/bin/activate && python -m pytest tests/test_api.py -q
  • git diff -- README.md agentflow/app.py docs/cli.md tests/test_api.py

Not run:

  • the full repository test suite is not claimed green in this PR body; this PR only claims the focused API regression coverage above

Disclosure notes

  • claims in this PR are bounded to the reviewed web API entrypoints and reproduced local validation path
  • the repository does not currently include a SECURITY.md
  • no unrelated source files were changed outside the web API guard, docs, and focused tests

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.

2 participants