# REPRO-2026-00114: D-Tale Remote Code Execution via Custom Filter Input ## Summary Status: published Severity: critical Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00114 GHSA: GHSA-c87c-78rc-vmv2 CVE: CVE-2026-27194 ## Package Name: dtale Ecosystem: pip Affected: < 3.20.0 Fixed: 3.20.0 ## Root Cause # Root Cause Analysis Report ## GHSA-c87c-78rc-vmv2: D-Tale RCE via /save-column-filter ### Summary D-Tale versions prior to 3.20.0 are vulnerable to remote code execution (RCE) through the `/save-column-filter` API endpoint. The vulnerability exists because user-provided column filter values are interpolated into query strings that are passed to `pandas.DataFrame.query()` with the Python engine. When the Python engine is used, query expressions are evaluated as Python code using `eval()`, allowing attackers to inject and execute arbitrary Python code by crafting malicious filter expressions containing payloads like `__import__('os').system('id')`. ### Impact **Package:** dtale (PyPI) **Affected Versions:** < 3.20.0 **Fixed in:** 3.20.0 **CVE:** CVE-2026-27194 **Severity:** HIGH (CVSS 4.0: 8.1) **Risk:** - Unauthenticated attackers can execute arbitrary system commands on servers hosting D-Tale - Complete server compromise is possible - Data exfiltration, malware installation, and lateral movement are possible - The vulnerability is remotely exploitable without authentication when D-Tale is hosted publicly **Affected Components:** - `/dtale/save-column-filter/` endpoint - `dtale/column_filters.py` - StringFilter, NumericFilter, DateFilter, OutlierFilter classes - `dtale/query.py` - `run_query()` function ### Root Cause The vulnerability stems from a lack of input validation in the column filter system. When users create column filters through the UI or API, the filter configuration is serialized as JSON and stored. The filter values are then interpolated into query strings and passed to `pandas.DataFrame.query()`. In pandas, when `query()` is called with `engine='python'` (the default), it uses `pandas.core.computation.eval.eval()` which internally calls Python's `eval()` to evaluate the expression. This allows arbitrary Python code execution. **Vulnerable Code Flow:** 1. User sends request to `/save-column-filter/` with `col` and `cfg` parameters 2. `save_column_filter()` in `views.py` creates a `ColumnFilter` object 3. `ColumnFilter` instantiates filter-specific builders (StringFilter, OutlierFilter, etc.) 4. `build_filter()` constructs a query string by interpolating user input directly 5. The query is stored and later passed to `run_query()` 6. `run_query()` calls `df.query(query, engine='python')` which executes the malicious code **Example vulnerable code path (pre-3.20.0):** ```python # In StringFilter.build_filter() fltr["query"] = "`{}` == {}".format(build_col_key(self.column), state[0]) # User controls 'state[0]' and can inject: __import__('os').system('id') # In run_query() df = df.query( query if is_pandas25 else query.replace("`", ""), local_dict=context_vars or {}, engine=engine, # Default is 'python' ) ``` **Fix Commit:** https://github.com/man-group/dtale/commit/431c6148d3c799de20e1dec86c4432f48e3d0746 The fix adds validation at two layers: 1. **Filter-level validation:** Each filter type validates inputs before interpolation, blocking dangerous patterns like `__import__`, `exec()`, `eval()`, `@` references, `os.*`, etc. 2. **Query-level validation:** A defense-in-depth check in `run_query()` validates the final query string before calling `DataFrame.query()`. ### Reproduction Steps The reproduction is automated in `repro/reproduction_steps.sh`. **What the script does:** 1. Installs vulnerable D-Tale version 3.19.1 2. Creates a test DataFrame with sample data 3. Tests `StringFilter` with malicious code injection payload (`__import__('os').system(...)`) 4. Tests `OutlierFilter` with direct query injection (most dangerous - accepts raw query strings) 5. Checks for presence of security validation classes/functions that were added in the fix 6. Reports vulnerability status based on whether filters accept malicious input **Evidence of Reproduction:** The script confirms the vulnerability through these indicators: ``` [2] Testing StringFilter with code injection payload... [WARNING] Filter accepted malicious value! [WARNING] Generated query: `name` == "__import__('os').system('echo RCE')" [CRITICAL] Malicious code appears in query string! [3] Testing OutlierFilter with direct query injection... [WARNING] OutlierFilter accepted raw query! [CRITICAL] Query content: __import__('os').system('id') [CRITICAL] Raw malicious code in query - RCE possible! [4] Checking for security validation in column_filters.py... [VULNERABLE] ColumnFilterSecurity class NOT found [VULNERABLE] _DANGEROUS_PATTERNS regex NOT found [VULNERABLE] validate_query_safety() NOT found in query.py ``` **Exit Codes:** - 0: Vulnerability confirmed (vulnerable version) - 1: Fixed/patched version detected - 2: Uncertain ### Evidence **Log Files:** - `logs/install.log` - Installation output - `logs/test_output.log` - Full test execution output **Key Evidence from Test:** The vulnerable version allows the following filter configurations without any validation: 1. **StringFilter accepts malicious value:** - Input: `value = ["__import__('os').system('echo RCE')"]` - Generated query: `` `name` == "__import__('os').system('echo RCE')" `` - This would execute when `df.query()` is called 2. **OutlierFilter accepts raw malicious query:** - Input: `query = "__import__('os').system('id')"` - This is passed directly to `df.query()` without any sanitization 3. **Missing security controls:** - No `ColumnFilterSecurity` class (added in fix) - No `_DANGEROUS_PATTERNS` regex (added in fix) - No `validate_query_safety()` function (added in fix) ### Recommendations / Next Steps **Immediate Actions:** 1. **Upgrade to D-Tale 3.20.0+** - This is the only complete fix 2. If upgrading is not immediately possible, avoid exposing D-Tale instances to untrusted networks 3. Review existing D-Tale deployments for signs of compromise **Fix Details:** The fix (commit 431c6148) implements defense-in-depth validation: 1. **Input validation patterns:** Blocks: - Dunder attributes (`__import__`, `__class__`, etc.) - Dangerous function calls (`import()`, `exec()`, `eval()`, `compile()`, `open()`, etc.) - Module access patterns (`os.*`, `sys.*`, `subprocess`, `shutil.*`) - Variable references (`@variable` syntax in pandas query) 2. **Filter-specific validation:** - `validate_string_value()` - Validates string filter inputs - `validate_numeric_value()` - Ensures numeric values are actually numbers - `validate_date_value()` - Validates date format and characters - `validate_outlier_query()` - Validates outlier filter query strings - `validate_operand()` - Validates comparison operands are in allowed set 3. **Query-level validation:** - `validate_query_safety()` - Final check before `df.query()` execution - Catches any dangerous patterns that bypass filter-level validation **Testing Recommendations:** 1. Run `repro/reproduction_steps.sh` after upgrade to confirm fix is in place 2. Test legitimate filter operations still work (string equals, numeric ranges, date filters) 3. Monitor for any filter-related errors in application logs ### Additional Notes **Idempotency Confirmation:** The reproduction script has been executed twice consecutively and produced identical results (vulnerability confirmed) on both runs. The script: - Installs the vulnerable version fresh each time - Cleans up test files after execution - Does not modify system state beyond pip install **Related Vulnerabilities:** This is part of a class of vulnerabilities in D-Tale involving arbitrary code execution through pandas: - GHSA-832w-fhmw-w4f4 (CVE-2024-55890) - RCE through custom filters - CVE-2025-0655 - Related RCE in custom filter input **Limitations:** - The reproduction demonstrates that malicious code reaches `df.query()`, but does not actually trigger code execution due to pandas' own string quoting in some contexts - The OutlierFilter is the most dangerous as it accepts raw query strings without quoting - Real-world exploitation would require the malicious filter to be saved and then applied to data ## Reproduction Details Reproduced: 2026-02-20T15:56:37.160Z Duration: 713 seconds Tool calls: 112 Turns: 77 Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00114 pruva-verify GHSA-c87c-78rc-vmv2 pruva-verify CVE-2026-27194 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00114&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00114/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/advisories/GHSA-c87c-78rc-vmv2 - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-27194 ## Artifacts - repro/rca_report.md (analysis, 8123 bytes) - repro/reproduction_steps.sh (reproduction_script, 9304 bytes) - bundle/ticket.json (other, 3618 bytes) - bundle/ticket.md (ticket, 1088 bytes) - bundle/source.json (other, 2127 bytes) - logs/install.log (log, 222 bytes) - logs/variant_test_output.log (log, 4299 bytes) - logs/test_output.log (log, 1898 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00114 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00114/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00114 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev