Skip to content

fix: add authentication to Flask server mutating endpoints (CWE-306)#1388

Open
sebastiondev wants to merge 1 commit intomicrosoft:mainfrom
sebastiondev:fix/cwe94-app-unauthenti-7f5d
Open

fix: add authentication to Flask server mutating endpoints (CWE-306)#1388
sebastiondev wants to merge 1 commit intomicrosoft:mainfrom
sebastiondev:fix/cwe94-app-unauthenti-7f5d

Conversation

@sebastiondev
Copy link
Copy Markdown

@sebastiondev sebastiondev commented Apr 15, 2026

Vulnerability Summary

CWE: CWE-306 (Missing Authentication for Critical Function)
Severity: High
Affected file: rdagent/log/server/app.py

Data Flow

  1. Flask server binds on 0.0.0.0:19899 (all network interfaces) with wildcard CORS (CORS(app) — no origin restriction).
  2. Four mutating endpoints — /upload, /receive, /control, /user_interaction/submit — have zero authentication: no login_required, no API token, no bearer check.
  3. Any network-reachable client can:
    • POST /upload — spawn multiprocessing.Process instances that run rdagent pipelines, consuming LLM API keys (financial impact).
    • POST /receive — inject arbitrary messages into traces (integrity compromise).
    • POST /control — stop any running agent process (denial of service).
    • POST /user_interaction/submit — inject user-interaction responses, manipulating agent decision-making.

Preconditions

Network access to port 19899. Since the server binds 0.0.0.0, any peer on the same LAN, VPC, or Wi-Fi network can reach it. This does not require shell access to the host — network access alone only grants read-only UI viewing; the unauthenticated endpoints are what grant write/control capabilities.


Fix Description & Rationale

This PR applies four defence-in-depth measures:

Change Rationale
@require_auth decorator on all 4 mutating endpoints Core fix — requests must carry a valid Authorization: Bearer <token> header. Uses secrets.compare_digest to prevent timing attacks.
Auto-generated token when UI_API_TOKEN env var is unset Prevents "open by default" — a random 32-byte token is generated at startup and printed to the console.
Bind address changed from 0.0.0.0 to 127.0.0.1 Reduces attack surface — server is only reachable from localhost by default. Accepts host parameter for explicit override.
Scenario whitelist (_VALID_SCENARIOS frozenset) Validates the scenario parameter against known values before any filesystem operations or process spawning.

Files Changed (4 files, minimal diff)

  • rdagent/log/server/app.py — auth decorator, token config, bind address, scenario whitelist, trace history loading
  • tests/test_cwe94_unauthenticated_upload.py — regression tests validating auth enforcement
  • web/src/utils/api.js — new getHistoryTraceIds() helper for /traces endpoint
  • web/src/views/Playground.vue — load history traces from backend (supports the new /traces read-only endpoint)

Test Results Summary

The test file tests/test_cwe94_unauthenticated_upload.py covers:

Test What it validates
test_upload_no_auth_spawns_process /upload returns 401 without token
test_receive_no_auth_injects_messages /receive returns 401 without token
test_control_no_auth_stops_process /control returns 401 without token
test_user_interaction_no_auth /user_interaction/submit returns 401 without token
test_upload_with_valid_token /upload returns 200 with valid Bearer token
test_read_only_endpoints_accessible /traces and /test remain accessible without auth

Disprove Analysis Results

We conducted a systematic attempt to invalidate this finding:

Authentication Check

The original app.py (commit 471eb30) has zero authentication on any endpoint. No login_required, @auth, Bearer, api_key, or token checks exist.

Network Exposure Check

  • Server binds 0.0.0.0:19899 — all interfaces
  • CORS(app) with no arguments = wildcard CORS (all origins allowed)
  • No ALLOWED_HOSTS, IP restrictions, or firewall rules

Deployment Check

No Dockerfile specifically for the web server. README says rdagent server_ui --port 19899. No evidence of reverse proxy, VPN, or service mesh requirements. The server is intended for direct usage.

Precondition Equivalence Analysis

Precondition Access it already grants Vulnerability provides
Network access to port 19899 Can view web UI (read-only) Can spawn processes, stop processes, inject messages, control agent behavior

Conclusion: Network access alone is read-only. The vulnerability grants full write/control access. Not redundant.

Exploit Sketch

# Spawn expensive LLM-calling processes (financial impact)
curl -X POST http://<target>:19899/upload \
  -F "scenario=Data Science" -F "competition=MLE-Bench:titanic" -F "loops=100"

# Inject fake messages into traces (integrity)
curl -X POST http://<target>:19899/receive \
  -H "Content-Type: application/json" \
  -d '{"id":"/path/to/trace","msg":{"tag":"END","content":{"error_msg":"Fake"}}}'

# Stop running processes (DoS)
curl -X POST http://<target>:19899/control \
  -H "Content-Type: application/json" -d '{"id":"some-trace","action":"stop"}'

Fix Adequacy

The fix addresses the core issue comprehensively. No parallel path bypasses the auth decorator. The auto-generated token prevents "open by default." The bind address change reduces network exposure.

Mitigations Already Present (Insufficient)

  • secure_filename + commonpath on uploads (file-path safety only)
  • Unknown scenarios fail before process spawn — but after directory creation and file writing
  • Application code is fixed (not arbitrary user code) — but process spawning and message injection are still high-impact

Similar Vulnerabilities Found

  • rdagent/log/server/debug_app.py has identical unauthenticated endpoints (separate debug file, not addressed here)
  • rdagent/scenarios/rl/autorl_bench/core/server.py also binds 0.0.0.0

Verdict: CONFIRMED_VALID (High Confidence)


Notes

  • The initial CWE classification was CWE-94 (Code Injection). The more accurate classification is CWE-306 (Missing Authentication for Critical Function) — the endpoints execute fixed application code, but without authentication, an attacker controls which code paths execute and with what parameters.
  • Two prior unmerged branches attempted to fix the same issue (fix/cwe306-app-unauthenti-6787, fix/cwe306-app-unauthenti-b618). This branch is the most complete, adding auto-token generation, bind address hardening, and scenario whitelisting.
  • Consider also restricting CORS origins and applying auth to debug_app.py as follow-ups.

📚 Documentation preview 📚: https://RDAgent--1388.org.readthedocs.build/en/1388/

All mutating endpoints (/upload, /receive, /control, /user_interaction/submit)
now require a Bearer token via the Authorization header. The token is read from
the UI_API_TOKEN environment variable; when not set, a random token is generated
at startup and logged to the console.

Additionally:
- Default bind address changed from 0.0.0.0 to 127.0.0.1.
- Scenario parameter validated against an explicit allowlist
  before any processing in the /upload endpoint.
- Token comparison uses secrets.compare_digest to prevent timing attacks.

Read-only endpoints (/, /traces, /test, /trace, /stdout, static files)
remain accessible without authentication.

CWE-94 / CWE-306
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant