qwed Vulnerable to Authenticated Remote Code Execution via Unsafe SymPy `parse_expr()`
Published Aug 25, 2026 · Updated Oct 23, 2026 · Also known as GHSA-q27q-98j4-9pfv
Original summary
Normalized public record · Updated Oct 23, 2026
qwed Vulnerable to Authenticated Remote Code Execution via Unsafe SymPy `parse_expr()`
Publisher and source text is retained separately from QFinch interpretation.
QFinch assessment
The qwed package (version 5.1.1) passes attacker-controlled input directly to SymPy's parseexpr() function without a restricted namespace. Because parseexpr() internally calls Python's eval(), any authenticated tenant can execute arbitrary Python code inside the API server process. The attack requires only a standard user account, which is freely obtainable through the default-enabled /auth/signup endpoint. Successful exploitation gives the attacker full read/write access to the filesystem and the ability to execute operating system commands, resulting in complete server compromise.
Potential impact
This is an Authenticated Remote Code Execution vulnerability. Any user who can create a tenant account (which is possible by default, since /auth/signup requires no invitation or administrator approval) can execute arbitrary Python code inside the API server process with the privileges of the server's operating system user. Concrete impact includes: - Confidentiality — read any file accessible to the server process (environment variables, secret keys, database contents, source code). - Integrity — write or overwrite any file accessible to the server process, modify database records, plant backdoors. - Availability — terminate the server process, exhaust resources, corrupt persistent storage. In a shared multi-tenant deployment, a single tenant can compromise the entire server, affecting all other tenants' data. In a containerized deployment, the immediate impact is container-level compromise; lateral movement depends on the container's network and volume configuration.
Technical context
The vulnerability exists in two independently reachable code paths: Primary sink — POST /verify/math src/qwednew/api/main.py:442 defines the /verify/math route, protected only by getcurrenttenant (line 444), which accepts any valid tenant API key. The request body field expression is read at line 463 and passed through a cosmetic regex normalization at line 495 (re.sub(r'(\d)(\()', r'\1\2', expression)) that performs no security validation. The normalized string is then passed directly to parseexpr() at line 504: Secondary sink — POST /verify/batch src/qwednew/api/main.py:1481 defines the /verify/batch route. Batch items flow through batchservice.createjob() (line 1517) into batch.py:132 where item.query is stored verbatim, then processed by verifyitem() (line 167). When the item type is VerificationType.MATH (line 222), the expression is passed to parseexpr() at line 239 with no sanitization: parseexpr() accepts a globaldict and localdict parameter that, when set to {"builtins": {}} and an allowlist respectively, restrict what names are accessible during evaluation. Neither call site sets these parameters, leaving the full Python built-in namespace available to the attacker.
Evidence: public source recordsAffected products
| Affected package, product or range | Fix status |
|---|---|
| Consult the referenced vendor advisory for affected versions. | Consult source advisory |
Matching a version is only one part of assessing exposure. QFinch has not assessed your environment on this public page.
Remediation
Apply the vendor-supported fixed release or mitigation, then verify the affected component is no longer reachable.
- Confirm the affected product and reachable code path exist in your environment.
- Apply the vendor-supported patch or mitigation from the primary advisory.
- Rescan the exact deployed revision and retain verification evidence.
Evidence coverage
- SSVC priority
- MEDIUM
- Confidence
- 10%
- Aliases
- GHSA-q27q-98j4-9pfv
Scores appear only when corresponding evidence exists. Missing data is never interpreted as zero risk.
Sources and record history
Record history
Published Aug 25, 2026 · Last refreshed Oct 23, 2026