Skip to content

[security] fix(web): protect web control plane by default#4

Closed
Hinotoi-agent wants to merge 1 commit intoShun-Calvin:mainfrom
Hinotoi-agent:fix/pr87-web-auth
Closed

[security] fix(web): protect web control plane by default#4
Hinotoi-agent wants to merge 1 commit intoShun-Calvin:mainfrom
Hinotoi-agent:fix/pr87-web-auth

Conversation

@Hinotoi-agent
Copy link
Copy Markdown

Summary

This PR hardens the web frontend control plane added on the PR HKUDS#87 branch so it is not network-exposed without an explicit authentication boundary.

It makes three focused changes:

  • binds oh web to loopback by default instead of all interfaces
  • requires bearer authentication before non-loopback deployments can expose API and Socket.IO control paths
  • adds regression coverage for unauthenticated /api/config and /api/submit requests

Security issues covered

Issue Impact Severity
Unauthenticated web control plane A network-reachable caller could change permission mode and submit prompts to the running OpenHarness agent session High when the web server is reachable beyond localhost

Before this PR

  • oh web defaulted to 0.0.0.0, making the web backend reachable from other hosts on the same network or any exposed interface.
  • /api/config accepted unauthenticated state-changing requests, including permission_mode updates that normalize auto to full_auto.
  • /api/submit accepted unauthenticated prompts and forwarded them into the active runtime via backend_host.handle_request(...).
  • Socket.IO accepted clients without authentication and used wildcard CORS.

After this PR

  • oh web and run_web_server() default to 127.0.0.1.
  • Binding to a non-loopback host such as 0.0.0.0 now requires an explicit bearer token via --auth-token or OPENHARNESS_WEB_AUTH_TOKEN.
  • /api/* routes require Authorization: Bearer <token> when a web auth token is configured, while /api/health remains unauthenticated for health checks.
  • Socket.IO clients must provide matching auth metadata when a web auth token is configured.
  • Socket.IO no longer uses wildcard CORS.
  • Regression tests cover the previously vulnerable config and submit paths.

Why this matters

The web backend is a control plane for a running coding agent. If it is reachable from a network interface without authentication, an unauthenticated caller can influence runtime settings and submit arbitrary prompts into the agent session.

The most important boundary is therefore not just “browser UI access”; it is whether a remote client can reach endpoints that mutate agent behavior or drive the runtime.

Attack flow

remote HTTP client
    -> POST /api/config {"permission_mode":"auto"}
        -> runtime permission mode becomes full_auto
            -> POST /api/submit {"line":"..."}
                -> prompt is submitted to the active OpenHarness runtime

Affected code

Issue Files
Web control-plane exposure src/openharness/cli.py, src/openharness/web_server.py
Regression coverage tests/test_web_server_security.py

Root cause

Unauthenticated web control plane:

  • The web server defaulted to an all-interface bind address.
  • HTTP and Socket.IO control-plane handlers trusted any reachable client.
  • State-changing routes and prompt submission routes did not share an authentication gate.

CVSS assessment

Issue CVSS v3.1 Vector
Unauthenticated web control plane 8.8 High CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Rationale: when the web port is network-reachable, no credentials or user interaction are required to drive control-plane behavior that can affect agent confidentiality, integrity, and availability. The practical impact still depends on the deployment being reachable beyond localhost and on the runtime permissions/tools available to the agent.

Safe reproduction steps

On the vulnerable PR HKUDS#87 branch, a local TestClient harness with a fake runtime bundle showed that unauthenticated requests reached the runtime sinks:

CONFIG_STATUS 200 {'status': 'success', 'settings': {'permission_mode': 'auto'}}
SUBMIT_STATUS 200 {'status': 'accepted'}
('app_state.set', {'permission_mode': 'full_auto'})
('engine.set_permission_checker', 'PermissionChecker')
('broadcast', 'config_saved')
('handle_request', None, {'type': 'submit_line', 'line': 'Use Bash to run: id > /tmp/openharness-web-pwned'})

The new regression tests encode the same boundary with fake settings/runtime objects so the test does not write real local OpenHarness configuration.

Expected vulnerable behavior

Before this PR, unauthenticated callers could receive successful responses from both:

POST /api/config
POST /api/submit

and those requests could update runtime permission state and submit a prompt into the agent session.

Changes in this PR

  • Change the web server default host from 0.0.0.0 to 127.0.0.1 in both CLI and server defaults.
  • Add --auth-token and OPENHARNESS_WEB_AUTH_TOKEN support for explicit authenticated web deployments.
  • Reject non-loopback binds unless a web auth token is configured.
  • Add bearer-token middleware for /api/*, excluding /api/health.
  • Require matching Socket.IO auth metadata when a web auth token is configured.
  • Remove wildcard Socket.IO CORS and use the default same-origin policy.
  • Add focused regression tests for the web control-plane boundary.

Files changed

Category Files What changed
CLI defaults src/openharness/cli.py default host changed to loopback; --auth-token added
Web backend src/openharness/web_server.py bearer auth, non-loopback guard, Socket.IO auth, same-origin Socket.IO CORS default
Tests tests/test_web_server_security.py regression coverage for default binding, non-loopback opt-in, unauthenticated denial, and authenticated access

Maintainer impact

  • Local development remains simple: oh web continues to work on 127.0.0.1 without a token.
  • Operators who intentionally expose the web server on a non-loopback host must make an explicit authenticated choice.
  • The patch is limited to the new web frontend/server surface and does not change terminal UI behavior.

Fix rationale

The safest default for a local agent control plane is loopback-only access. Non-loopback access is still available, but it must be paired with an explicit authentication token so deployments do not accidentally expose prompt submission and runtime configuration endpoints to a network.

The tests lock in both sides of that behavior: unauthenticated control-plane requests are denied when auth is configured, while correctly authenticated requests still reach the intended runtime handlers.

Type of change

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

Test plan

  • Reproduced the vulnerable behavior locally against PR feat(frontend) - Add web frontend and FastAPI web server HKUDS/OpenHarness#87 with a safe fake-runtime TestClient harness.
  • Ran the focused web security regression test file.
  • Compiled touched Python files.
  • Checked whitespace with git diff --check.
  • Ran Ruff lint on touched Python files.
  • Full repository test suite was not run; this PR is scoped to the new web control-plane boundary and focused tests were used.

Executed with:

PYTHONPATH=src:. uv run pytest -o addopts='' tests/test_web_server_security.py -q
PYTHONPATH=src:. uv run python -m compileall -q src/openharness/web_server.py src/openharness/cli.py tests/test_web_server_security.py
git diff --check
uv run ruff check src/openharness/web_server.py src/openharness/cli.py tests/test_web_server_security.py

Note: uv run ruff format --check reports broad pre-existing formatting churn in legacy files such as src/openharness/cli.py and src/openharness/web_server.py, so I did not include unrelated whole-file formatting changes in this security PR. The new test file was formatted with Ruff.

Disclosure notes

- bind web server to loopback by default
- require bearer auth for exposed API and Socket.IO control paths
- add regression coverage for unauthenticated config and submit requests
@Hinotoi-agent
Copy link
Copy Markdown
Author

Closing at the author's request.

@Hinotoi-agent Hinotoi-agent deleted the fix/pr87-web-auth branch May 5, 2026 05:00
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