Execute curl requests through a real Playwright browser when anti-bot protection gets in the way
CurlWright is a Python tool that takes a curl command, opens a real Chromium browser through Playwright, resolves Cloudflare and similar browser-side friction, and returns the final HTTP response in a form that still feels close to curl-driven workflows.
It is useful when a plain HTTP client is not enough because the target requires browser execution, JavaScript, cookies, challenge handling, or a persisted trusted session.
| Feature | Description |
|---|---|
| Browser-backed curl execution | Parse a curl command and execute it through Playwright |
| Cloudflare challenge handling | Detect and progress browser-side verification flows |
| Turnstile support | Includes dedicated handling for Turnstile-style flows |
| Trusted session reuse | Persist per-domain trust state and cookies between runs |
| JSON and SARIF outputs | Machine-readable output for automation and CI/security tooling |
| Headless and server mode | Works in local desktop mode or with --no-gui in CI/VPS environments |
| Python library mode | Use as a CLI or from Python code |
| Clean layered architecture | Explicit domain, application, infrastructure, and interfaces layers |
Methods -X/--request, -I/--head, -G/--get
Headers -H/--header
Body -d/--data, --data-raw, --data-binary, --data-urlencode
Cookies -b/--cookie
Auth -u/--user
Network -x/--proxy, -L/--location, -k/--insecure, --max-time
Input Forms Direct command (-c) or file (-f)
pip install curlwright
python -m playwright install chromiumgit clone https://github.com/seifreed/Curlwright.git
cd Curlwright
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -e ".[dev]"
python -m playwright install chromium# Execute a curl command directly
curlwright -c "curl https://example.com"
# Read the curl command from a file
curlwright -f request.txt
# Emit structured JSON for automation
curlwright -f request.txt --json-output# Basic request
curlwright -c "curl https://httpbin.org/get"
# Save the response body to a file
curlwright -f request.txt -o response.html
# Server/CI mode
curlwright -f request.txt --no-gui --json-output
# Persist diagnostics for failures
curlwright -f request.txt --artifact-dir .artifacts/run-1 --sarif-output report.sarif
# Increase retries and timeout
curlwright -c "curl https://target.example" --timeout 60 --retries 5 --delay 3| Option | Description |
|---|---|
-c, --curl |
Curl command to execute |
-f, --file |
File containing the curl command |
--headless |
Run Chromium headless |
--no-gui |
Server-oriented mode without display requirements |
--user-agent |
Override the browser user agent |
--timeout |
Request timeout in seconds |
--cookie-file |
Override cookie persistence path |
--state-file |
Override trusted-session state path |
--artifact-dir |
Directory for diagnostics, screenshots, HTML and logs |
--profile-dir |
Override the persistent Chromium profile directory |
--no-persist-cookies |
Disable automatic cookie load/save |
--bypass-attempts |
Challenge-resolution attempts per request |
--retries |
Retry count after failures |
--delay |
Delay between retries |
-o, --output |
Save the rendered response output to a file |
-v, --verbose |
Print a runtime execution summary |
--json-output |
Emit the stable JSON contract |
--sarif-output |
Write a SARIF 2.1.0 report |
--json-output emits a stable machine-readable structure:
{
"schema_version": 1,
"kind": "curlwright-result",
"ok": true,
"exit_code": 0,
"response": {
"status": 200,
"url": "https://example.com/",
"headers": {},
"body": "..."
},
"meta": {}
}Failure JSON includes error, error_type, exit_code, and for bypass failures also artifact_dir plus an assessment block.
import asyncio
from curlwright import RequestExecutor
async def main() -> None:
executor = RequestExecutor(headless=True, timeout=30)
result = await executor.execute('curl -H "Accept: application/json" https://httpbin.org/get')
print(result["status"])
print(result["url"])
print(result["body"][:120])
await executor.close()
asyncio.run(main())from curlwright import CurlParser
parser = CurlParser()
request = parser.parse('curl -X POST -H "Content-Type: application/json" https://api.example.com')
print(request.method)
print(request.url)
print(request.headers)import asyncio
from curlwright import RequestExecutor
from curlwright.utils import CookieManager
async def main() -> None:
cookies = CookieManager("cookies.pkl")
executor = RequestExecutor(headless=True, cookie_file="cookies.pkl")
result = await executor.execute("curl https://example.com")
print(result["status"])
await executor.close()
asyncio.run(main())curlwright -c 'curl -H "Authorization: Bearer TOKEN" https://api.example.com/data' --json-outputCreate request.txt:
curl -X GET \
-H "Accept: application/json" \
-H "User-Agent: Analyst/1.0" \
-b "session=abc123" \
https://protected.example.com/api/dataRun it:
curlwright -f request.txt -o response.jsonmkdir -p .artifacts/job-1
curlwright \
-f request.txt \
--no-gui \
--json-output \
--sarif-output .artifacts/job-1/curlwright.sarif \
--artifact-dir .artifacts/job-1 \
--state-file .artifacts/job-1/state.json \
--cookie-file .artifacts/job-1/cookies.pkl \
> .artifacts/job-1/result.jsoncurlwright -c "curl https://target.example" --headless --timeout 90 --retries 5 --delay 2The active codebase follows an explicit layered structure:
curlwright/
domain/ Pure models, policies and ports
application/ Use cases and orchestration
infrastructure/ Playwright, persistence and parser adapters
interfaces/ CLI, JSON and SARIF presenters
bootstrap.py Composition root
This separation keeps browser automation and heuristics in infrastructure while the policy and use-case flow stay isolated from Playwright details.
- Python
>=3.13,<3.15 - Playwright Chromium installed via
python -m playwright install chromium - See
pyproject.tomlfor the full package metadata and dependency declarations
Contributions are welcome. If you want to change behavior, add support for more curl flags, or improve challenge handling, open an issue or send a pull request.
- Fork the repository
- Create your branch:
git checkout -b feature/my-change - Run the test suite
- Commit your changes
- Push the branch
- Open a pull request
If CurlWright is useful in your workflows, you can support the project here:
This project is licensed under the MIT License. See LICENSE for details.
Attribution
- Author: Marc Rivero | @seifreed
- Repository: github.com/seifreed/Curlwright
Built for browser-backed automation and resilient protected-request workflows