Repository navigation
Home
This guide documents CorsOne as implemented in version 2.0.0. The code and pyproject.toml are the source of truth; options mentioned in older documentation may not be implemented.
- Introduction
- How CorsOne works
- Requirements and installation
- Quick start
- CLI reference
- Usage examples
- Configuration
- Understanding scan results
- Output formats and integration
- Security, privacy, and safe operation
- Troubleshooting
- Development and testing
- Project structure
- Known limitations
- Frequently asked questions
- Contributing and security reporting
- Version and release information
- License and acknowledgments
CorsOne is a Python command-line utility for sending CORS-related Origin header test cases to HTTP endpoints and recording the response headers. It is intended for developers and security testers evaluating systems they own or are explicitly authorized to assess.
CorsOne can help identify responses that merit further investigation. It does not emulate a victim's browser or establish that victim-specific authenticated content can be read cross-origin. If custom credentials are supplied as headers, CorsOne sends those headers as configured.
- A target is supplied as a URL, a line in a target file, or a line on standard input. A bare domain is normalized to an HTTPS URL.
- CorsOne extracts the target's network location to construct payloads, then sends requests to the target URL. It uses the selected
GETorPOSTmethod, sends no request body, does not follow redirects, and sets one generated value in theOriginrequest header. - The payload generator currently returns 51 distinct values. They cover reflected and
nullorigins; target-like, subdomain, scheme, and suffix cases; numbered hostname/punctuation variants; and values intended to probe permissive regular-expression rules. Several candidates deliberately contain characters or forms that a browser would not normally serialize as an origin. - CorsOne reads
Access-Control-Allow-CredentialsandAccess-Control-Allow-Originfrom the response. It labels a response vulnerable when credentials aretrue(case-insensitive) and the allow-origin value is either exactly the submitted test value or exactly the target URL's scheme and authority. - Results are rendered as text, JSON, or a SARIF 2.1.0-shaped document.
The target-origin alternative in step 4 is broader than matching the submitted attacker origin. Therefore, a VULNERABLE result can be a false positive: verify that the response actually authorizes the tested origin before treating it as an exploitable cross-origin vulnerability. Conversely, a negative scan does not establish that all CORS configurations or browser behaviors are safe.
The generator contains 51 dictionary entries with 51 unique values. This is the number of test requests per target when all checks run; --stop-on-first can reduce the number of requests. The payloads are generated from the target's network location and the configured custom domain. They are test inputs, not a guarantee of complete CORS coverage.
- Python
>=3.10,<3.15(Python 3.10 through 3.14). - Runtime dependencies declared in
pyproject.toml:aiohttpaiodnsvalidatorscolorama
- The project metadata does not restrict supported operating systems. The configured GitHub Actions workflow runs on Ubuntu; Windows is used for local development in this repository. macOS is not included in the configured CI matrix.
Windows PowerShell:
git clone https://github.com/omranisecurity/CorsOne.git
cd CorsOne
py -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install .For an editable install suitable for development, replace the final command with:
python -m pip install -e ".[dev]"Linux or macOS shell:
git clone https://github.com/omranisecurity/CorsOne.git
cd CorsOne
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install .Use python -m pip install -e '.[dev]' instead when setting up a development environment.
The package metadata defines the corsone console command. The repository also supports python -m corsone; CorsOne.py is a backwards-compatible launcher when running from a source checkout. These instructions install from the repository; they do not assert that a release is published on PyPI.
corsone --version
python -m corsone --helpThe version is currently 2.0.0.
In the environment where CorsOne was installed:
python -m pip uninstall corsoneStart a local test web application that you control and make an endpoint available at http://127.0.0.1:8000/. Then run:
corsone --url http://127.0.0.1:8000/ --silentThe URL is passed with --url; --silent omits the startup banner. In the default text format, results look like:
SAFE Reflected Origin: https://attacker.com
This is an illustrative output line, not a result from a real target. Use only systems you own or have explicit permission to test.
The CLI is implemented in src/corsone/cli.py. Run corsone --help for the installed command's help. Only one target input source can be selected at a time. A target source is not formally required by argparse, but a target URL, target file, or piped input is required to perform a useful scan.
| Option | Required | Type / accepted values | Default | Purpose and validation |
|---|---|---|---|---|
-h, --help
|
No | Flag | — | Show argparse help and exit. |
-u, --url URL
|
One input source for a single target | URL or domain | None | Scan one target. Full URLs are validated; a valid bare domain is prefixed with https://. |
-l, --list PATH
|
One input source for a target list | File path | None | Read one URL/domain per non-empty line. Each line is normalized like --url. |
| stdin | One input source for piped targets | One URL/domain per line | Not used when stdin is a terminal | If stdin is not a terminal and neither --url nor --list is set, read targets from stdin. |
-m, --method {GET,POST}
|
No |
GET or POST
|
GET |
HTTP method. POST requests have no configured body. |
-sof, --stop-on-first
|
No | Flag | Off | Test payloads sequentially and stop for each target after the first candidate classified as vulnerable. |
-cd, --custom-domain DOMAIN
|
No | Domain name | attacker.com |
Set the attacker-domain component used by generated payloads. Validation rejects schemes, paths, empty values, and invalid domains. |
-H, --headers JSON
|
No | JSON object of string keys and string values | None | Add custom request headers. Since custom headers are applied after the generated Origin header, a supplied Origin key overrides each payload; avoid doing that unless intentional. |
-p, --proxy URL
|
No | HTTP/HTTPS proxy URL | None | Send requests through an HTTP or HTTPS proxy. SOCKS schemes are rejected. |
--insecure |
No | Flag | Off; TLS verification enabled | Disable TLS certificate verification. Use only in a controlled test when you understand the risk. |
-w, --workers N
|
No | Positive integer | 5 |
Maximum number of concurrent payload requests per target when not using --stop-on-first. |
-rl, --rate-limit SEC
|
No | Non-negative number | 0 |
Sleep for this many seconds after each probe. With concurrent workers, each task sleeps after its own response. |
-t, --timeout SEC
|
No | Positive integer | 10 |
Total request timeout in seconds. The connection timeout is separately set to 5 seconds. |
-r, --retries N
|
No | Non-negative integer | 3 |
Accepted for compatibility, but retries are not implemented; changing this value currently does not change requests. |
-o, --output PATH
|
No | File path | stdout | Write the selected report to a UTF-8 text file. Parent directories are not created automatically. |
-f, --format {txt,json,sarif}
|
No |
txt, json, or sarif
|
txt |
Select the report format. The format is not inferred from the output filename; specify it explicitly. |
--log PATH |
No | File path | None | Accepted by the parser but not currently used to create a log file. |
-vo, --vuln-only
|
No | Flag | Off | Include only results classified as vulnerable in the report. This also excludes request errors because they are not classified as vulnerable. |
-nc, --no-color
|
No | Flag | Off | Accepted for compatibility. Current report output is plain text and does not add color, so this option has no effect. |
-s, --silent
|
No | Flag | Off | Suppress the startup banner. Useful when sending JSON or SARIF to stdout. |
-v, --verbose
|
No | Flag | Off | Enable INFO logging. The scanner currently logs a start message for each target. |
--version |
No | Flag | Off | Print the installed package version and exit. |
- Empty or invalid URLs are rejected. A plain valid domain is normalized to HTTPS.
- Target-file blank lines are ignored; invalid non-empty lines stop argument processing with an error.
- Custom headers must be valid JSON representing a string-to-string object.
- Custom domains must be hostnames and must not include a protocol scheme.
- Proxy schemes must be
httporhttps, and the URL must include a hostname. - Worker count and timeout must be greater than zero. Retries and rate limit cannot be negative.
Scan one authorized endpoint:
corsone --url https://app.example.test/api --silentScan a file of authorized targets:
corsone --list targets.txt --workers 3 --rate-limit 0.2 --silentPipe targets from another command:
type targets.txt | corsone --silentOn Linux/macOS, the corresponding shell form is:
cat targets.txt | corsone --silentUse POST, with no request body:
corsone --url https://app.example.test/api --method POST --silentAdd custom headers:
corsone --url https://app.example.test/api --headers '{"X-Assessment": "authorized-test"}' --silentDo not put credentials in examples, source control, or shared shell history. CorsOne does not redact custom header values from the request itself.
Write JSON or SARIF to a file:
corsone --url https://app.example.test/api --format json --output corsone-report.json --silent
corsone --url https://app.example.test/api --format sarif --output corsone-report.sarif --silentUse --silent when writing structured output to stdout. Without it, the banner is printed before the report and stdout is not a standalone JSON/SARIF document.
Use a validated HTTP proxy:
corsone --url https://app.example.test/api --proxy http://127.0.0.1:8080 --silentCorsOne currently has no configuration file format and reads no environment variables for its settings. Configure scans through command-line options or the equivalent ScanConfig values when using the Python API.
There is no configuration precedence chain: CLI arguments are parsed and used to build the scan configuration. --url, --list, and stdin are mutually exclusive target sources.
--log, --retries, and --no-color are accepted CLI options but currently have no operational effect. See the Known limitations section.
The ScanResult model contains:
| Field | Meaning |
|---|---|
url |
Target URL that was requested. |
bypass_name |
Label for the generated test case. |
bypass_value |
The value used for that test's Origin header. |
is_vulnerable |
Boolean set by the scanner's current response-header rule. |
response_code |
HTTP status code; defaults to 0 if a request error occurred before a response. |
acac |
Access-Control-Allow-Credentials response header, or null/omitted when absent. |
acao |
Access-Control-Allow-Origin response header, or null/omitted when absent. |
error |
Timeout or aiohttp client error text when such an error was caught. |
timestamp |
Unix timestamp in seconds when the result object was created. |
The current implementation sets is_vulnerable only when:
-
Access-Control-Allow-Credentialsis present and its value equalstrue, ignoring case; and -
Access-Control-Allow-Originexactly equals either the submitted payload or the target's scheme and authority.
This is an implementation rule, not a browser exploitability proof. In particular, a response allowing the target's own origin does not by itself demonstrate that it allows the tested cross-origin origin. Treat the matching payload and response headers as evidence to inspect, not a final security conclusion.
The scanner does not validate whether the endpoint returns sensitive data, whether a victim browser can reach it with credentials, or whether other browser controls affect access. To validate a candidate, use a controlled test account and a page served from a different origin than the target, then make a credentialed browser request to the target endpoint and confirm whether the response body is readable by that page. For example, in that controlled page's developer console:
fetch("https://app.example.test/api", { credentials: "include" })
.then((response) => response.text())
.then((body) => console.log(body));Replace the example endpoint with an authorized test endpoint. A successful network response alone is not enough; the key question is whether browser JavaScript on the separate origin can read the response.
The scanner catches asyncio timeouts and aiohttp client errors and creates a result with response_code 0 and an error field. Such results have is_vulnerable=False. Text output therefore labels an error as SAFE; SARIF likewise records it as a note, while JSON can include the error text. A SAFE line can mean “no matching headers” or “request failed”; inspect JSON for error details or rerun with verbose logging.
A scan with no vulnerable results is not proof of safety. Coverage is limited to the generated values, supplied target path/method, the observed response, and the implementation's classification rule.
Use --format txt, --format json, or --format sarif. The default is text. Save to a file with --output PATH, or omit --output to write the report to stdout.
One line is rendered for each included result:
SAFE Reflected Origin: https://attacker.com
The text format does not include HTTP status, response headers, timestamps, or error detail.
JSON output is a top-level array of serialized ScanResult objects. Fields whose value is None are omitted. A representative response-backed result is:
[
{
"url": "https://app.example.test/api",
"bypass_name": "Reflected Origin",
"bypass_value": "https://attacker.com",
"is_vulnerable": false,
"response_code": 200,
"timestamp": 1791571200.0
}
]In actual output, acac and acao are omitted if null. The timestamp above is illustrative. Error results include error and use a response code of 0.
Parse a saved report with Python:
import json
with open("corsone-report.json", encoding="utf-8") as report_file:
results = json.load(report_file)SARIF output is a JSON document declaring version 2.1.0, with one driver rule (cors-bypass) and a result per included scan result. The result level is warning for candidates and note for other results. CorsOne emits a SARIF-shaped report; the project does not currently include a schema-validation test, so validate it with your consuming security tool before relying on it in a pipeline.
For stdout integration, pass --silent so the startup banner does not precede the JSON/SARIF document. When --output is used, the report is written to the requested file and the banner remains on stdout unless silenced.
- Obtain explicit authorization and agree on scope before scanning.
- A default full scan sends 51 requests per target. Use
--stop-on-first,--workers, and--rate-limitwith care; rate limiting and concurrency can affect the request rate but do not guarantee a safe production impact. - The scanner does not follow redirects. Redirect behavior and endpoint-specific routing may therefore affect observed results.
- TLS certificate verification is enabled by default.
--insecuredisables verification and should be limited to controlled testing; it is not a general solution to connectivity problems. - Custom headers may contain credentials and are sent to the target. Avoid storing secrets in commands or reports. A custom
Originheader overrides generated payloads and can invalidate the test. - Output can expose target URLs, response metadata, or errors. Protect report files and remove sensitive files when no longer needed.
- HTTP status alone does not determine the finding classification. The scanner evaluates CORS response headers even for non-success statuses.
- The scanner sends GET/POST requests only; POST has no body. Do not assume the request is harmless for a state-changing endpoint.
See SECURITY.md for the repository's private vulnerability-reporting guidance.
| Symptom | Likely cause | Recommended action |
|---|---|---|
No target URL(s) supplied... |
No URL, list, or piped input was provided while stdin is a terminal. | Supply --url, --list, or pipe one or more targets. |
Invalid URL |
The value is neither accepted as a URL nor a valid domain. | Include a valid hostname and, when needed, a scheme such as https://. |
| Target-list read error | The path does not exist or is not readable. | Check the path, permissions, and file encoding; save one target per line. |
| Custom headers validation error | The value is not a JSON object containing only string keys and string values. | Quote a JSON object, for example '{"X-Test":"value"}'; shell quoting differs between terminals. |
| Unsupported proxy scheme | The proxy is not HTTP or HTTPS (for example, SOCKS). | Use a supported HTTP/HTTPS proxy; SOCKS is not implemented. |
| Request times out | The target is slow, unreachable, or the selected timeout is too short. | Confirm the target is reachable and adjust --timeout; avoid raising load by blindly increasing workers. |
| TLS or connection error | Certificate validation, DNS, network, proxy, or server connectivity may be involved. | Check the endpoint and proxy configuration. Keep TLS verification enabled unless testing a controlled certificate scenario. |
A result says SAFE but the request failed |
Text reports classify any non-vulnerable result as SAFE, including caught request errors. | Inspect JSON for error and response_code: 0; use --verbose for target-level scan-start logs. |
| JSON/SARIF printed to stdout cannot be parsed | The startup banner was printed before the report. | Add --silent, or write the report to a file with --output. |
| Output-file write fails | The destination directory is missing or access is denied. | Choose a writable path; CorsOne does not create parent directories. |
--retries, --log, or --no-color appears ineffective |
These arguments are accepted but are not wired to active behavior. | Do not rely on them; retries, log-file output, and colored output are not currently implemented. |
Exact network error text depends on Python, aiohttp, and the operating system.
From a clone, create and activate a virtual environment, then install the development extras:
python -m venv .venvWindows PowerShell activation:
.\.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"Linux/macOS activation:
source .venv/bin/activate
python -m pip install -e '.[dev]'Run checks from the repository root:
pytest
ruff check .
mypy srcThe development extra includes the build frontend used by CI. After installing .[dev], build a distribution with:
python -m buildIf you are not using the development extra, install the build frontend separately with python -m pip install build.
The CI workflow runs on Ubuntu with Python 3.10, 3.12, and 3.14. It installs development extras, runs Ruff, mypy, pytest with coverage, builds the distribution, and runs pip-audit.
.
├── CorsOne.py # Backwards-compatible source-checkout launcher
├── pyproject.toml # Package metadata, dependencies, CLI entry point, tool config
├── src/
│ └── corsone/
│ ├── __init__.py # Package version
│ ├── __main__.py # python -m corsone entry point
│ ├── cli.py # Argument parser and CLI orchestration
│ ├── config.py # CLI-to-scan configuration
│ ├── models.py # ScanConfig and ScanResult
│ ├── payloads.py # Origin test-value generation
│ ├── scanner.py # Async HTTP scanning
│ ├── validators.py # URL, domain, headers, and proxy validation
│ └── reporting/ # Text, JSON, and SARIF-shaped output
├── tests/
│ ├── unit/
│ └── integration/
├── .github/workflows/ # CI and code-scanning workflows
├── README.md
├── wiki.md
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
├── CHANGELOG.md
└── LICENSE
- The finding rule accepts either the submitted origin or the target's own scheme and authority in
Access-Control-Allow-Origin; the latter may produce false positives for cross-origin access. Manual validation is required. - The scanner sends a finite set of 51 generated header values. It is not an exhaustive CORS audit.
- Some payloads are intentionally malformed or browser-noncanonical probes; a response to them is not necessarily evidence of a browser-usable origin.
- Request errors are stored as non-vulnerable results and can appear as
SAFEin text output. -
--retriesis parsed and validated but has no effect; no retry loop is implemented. -
--logis parsed but does not create a log file. -
--no-coloris parsed but current report output is already uncolored. - A custom
Originheader overrides the generated payload. - The CLI accepts GET and POST only, and POST sends no body.
- Redirects are not followed.
- The configured CI workflow does not test macOS.
- The report implementation does not currently have tests that validate JSON/SARIF schemas or every report edge case.
These are current implementation limitations, not claims about planned features.
It looks for responses that combine Access-Control-Allow-Credentials: true with a matching Access-Control-Allow-Origin value according to the current implementation rule. See How CorsOne works.
No. It is a candidate classification based on response headers. The target-origin matching alternative can produce false positives, and CorsOne does not verify browser access to sensitive data.
Only with explicit authorization and an agreed scope. A full scan sends 51 requests per target by default; assess the impact and coordinate rate limits before scanning.
No. It means the current test did not produce a response classified as vulnerable. It may also mask a caught request error in text output. It does not cover all possible origins, browser behaviors, endpoints, or application states.
Run corsone --version or python -m corsone --version.
Use the repository's GitHub Issues page for reproducible bugs and feature requests. Do not post vulnerability details publicly; follow SECURITY.md for private security reporting.
Contributions should be focused and include tests for behavior changes. Follow CONTRIBUTING.md to set up development dependencies and run checks. For scan logic or payload changes, explain the security impact and add regression tests where practical.
Report vulnerabilities privately using a repository security advisory or the reporting path described in SECURITY.md. Do not disclose details publicly before a fix is available.
The package version is defined in src/corsone/__init__.py and read dynamically by pyproject.toml. The current source version is 2.0.0. Check an installed copy with:
corsone --versionThe version is currently 2.0.0; official tagged releases are listed on the GitHub Releases page. Check corsone --version to see which version is installed. This guide does not imply that a particular package index release exists.
CorsOne is distributed under the MIT License. The repository does not currently document additional project-specific acknowledgments.