CLI Reference
Complete reference for the parlov binary — global flags, subcommands, output formats, invocation patterns, and exit codes.
parlov is invoked as a single binary with one subcommand per mode of operation. All flags use the --long-name form. There are no short flags.
parlov [--format <table|json|sarif>] <command> [command-specific flags]Global Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--format | table, json, sarif | table | Output format for all results. Applies to every subcommand. |
The --format flag is global — it can appear before or after the subcommand name.
Subcommands
parlov has two subcommands:
existence— Run a single status-code differential probe against one endpoint with one HTTP method.scan— Run all applicable elicitation strategies against one endpoint, across multiple methods and vectors.
Both subcommands target the same endpoint pattern and use the same baseline/probe differential model. The difference is scope: existence sends one probe pair and reports one result. scan builds a plan from all registered strategies, filters by risk and vector, and reports an array of findings.
parlov existence
Run a single differential probe. Sends one baseline request (known-existing ID) and one probe request (known-nonexistent ID) using the specified HTTP method, then compares the responses.
Synopsis
parlov existence \
--target <url-template> \
--baseline-id <id> \
[--probe-id <id>] \
[--method <METHOD>] \
[--header <Name: Value>]... \
[--body <body-template>]Flags
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--target | string | yes | — | URL template. {id} is replaced with the resource identifier. |
--baseline-id | string | yes | — | Known-existing resource ID. Used as the control input. |
--probe-id | string | no | random UUIDv4 | Known-nonexistent resource ID. Defaults to a random UUID, which is nonexistent by design. |
--method | string | no | GET | HTTP method to use for both requests. |
--header | string | no | — | Additional request header in Name: Value format. Repeatable — pass --header once per header. |
--body | string | no | — | Request body template. {id} is replaced with the resource identifier. Required when the API identifies resources via the request body rather than the URL path. |
Examples
Basic GET probe:
parlov existence \
--target "https://api.example.com/users/{id}" \
--baseline-id 1001Sends GET /users/1001 (baseline) and GET /users/<random-uuid> (probe). Compares the two responses.
POST with body-based identifier:
parlov existence \
--target "https://api.example.com/auth/login" \
--baseline-id "alice@example.com" \
--probe-id "nonexistent@example.com" \
--method POST \
--body '{"email": "{id}", "password": "wrong"}'The {id} placeholder in both --target and --body is replaced with the baseline and probe IDs respectively.
With authentication headers:
parlov existence \
--target "https://api.example.com/documents/{id}" \
--baseline-id doc-42 \
--method GET \
--header "Authorization: Bearer eyJ..." \
--header "Accept: application/json"parlov scan
Run all applicable elicitation strategies against a target endpoint. Builds a plan from registered strategies, filters by risk ceiling or vector selection, executes each strategy, and reports an array of findings.
Synopsis
parlov scan \
--target <url-template> \
--baseline-id <id> \
[--probe-id <id>] \
[--risk <level>] \
[--vector <filter>]... \
[--header <Name: Value>]... \
[--body <body-template>] \
[--alt-credential <Header>] \
[--known-duplicate <field=value>] \
[--state-field <field=value>]Flags
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--target | string | yes | — | URL template. {id} is replaced with the resource identifier. |
--baseline-id | string | yes | — | Known-existing resource ID (baseline). |
--probe-id | string | no | random UUIDv4 | Known-nonexistent resource ID (probe). |
--risk | safe, method-destructive, operation-destructive | no | safe | Maximum risk level of strategies to execute. Only strategies at or below this ceiling are included in the plan. Mutually exclusive with --vector. |
--vector | string | no | — | Filter strategies by detection vector. Repeatable. Format: name or name:risk-level. Mutually exclusive with --risk. See Vector Filtering below. |
--header | string | no | — | Additional request header in Name: Value format. Repeatable. |
--body | string | no | — | Request body template. {id} is replaced with the resource identifier. |
--alt-credential | string | no | — | Under-scoped credential header for the scope-manipulation strategy, e.g. Authorization: Bearer <limited-token>. |
--known-duplicate | string | no | — | Known-duplicate value for the uniqueness strategy, in field=value format, e.g. email=alice@example.com. |
--state-field | string | no | — | Invalid state transition value for the state-transition strategy, in field=value format, e.g. status=invalid_state. |
--risk and --vector Are Mutually Exclusive
--risk sets a global risk ceiling — all strategies at or below that level are included.
--vector selects specific detection vectors and optionally overrides the risk level per vector. When --vector is provided, the risk ceiling is derived from the vector filters, not from --risk.
Passing both --risk (with a non-default value) and --vector is an error:
error: --vector and --risk are mutually exclusiveIf you need per-vector risk control, use --vector with inline risk levels. If you want a blanket risk ceiling across all vectors, use --risk.
Vector Filtering
The --vector flag accepts two formats:
name— include all strategies for this vector at their default risk level.name:risk-level— include strategies for this vector up to the specified risk level.
Vector names: status-code-diff, cache-probing, error-message-granularity.
The flag is repeatable — pass it once per vector filter.
# Only cache-probing strategies, safe risk only
parlov scan --target "..." --baseline-id 42 \
--vector cache-probing
# Both vectors, cache-probing up to method-destructive
parlov scan --target "..." --baseline-id 42 \
--vector status-code-diff \
--vector cache-probing:method-destructiveStrategy-Specific Flags
Some scan strategies require additional input to function. These flags enable those strategies — if the flag is omitted, the strategy that depends on it is skipped.
--alt-credential — Enables the scope-manipulation strategy. Provide a credential header with fewer privileges than the primary --header credential. The scan compares the server's response to the full-privilege credential against the under-scoped credential to detect scope-dependent differentials.
parlov scan \
--target "https://api.example.com/resources/{id}" \
--baseline-id 42 \
--header "Authorization: Bearer $FULL_TOKEN" \
--alt-credential "Authorization: Bearer $LIMITED_TOKEN"--known-duplicate — Enables the uniqueness strategy. Provide a field and value that already exists in the system. The scan sends a creation request with this duplicate value to observe whether the server returns a conflict (e.g., 409) for existing values but a different response for nonexistent ones.
parlov scan \
--target "https://api.example.com/users/{id}" \
--baseline-id 42 \
--risk method-destructive \
--known-duplicate "email=alice@example.com"--state-field — Enables the state-transition strategy. Provide a field and an invalid state value. The scan sends a state-change request with this invalid value to observe whether the server returns a conflict (e.g., 409) for existing resources but 404 for nonexistent ones.
parlov scan \
--target "https://api.example.com/orders/{id}" \
--baseline-id 42 \
--risk method-destructive \
--state-field "status=invalid_state"The {id} Template
Both --target and --body use {id} as a placeholder for the resource identifier. On each request, parlov replaces every occurrence of {id} with the appropriate ID — the baseline ID for the control request, the probe ID for the experimental request.
The placeholder works in URLs:
--target "https://api.example.com/users/{id}/profile"In request bodies:
--body '{"user_id": "{id}", "action": "lookup"}'And in both simultaneously:
--target "https://api.example.com/users/{id}" \
--body '{"id": "{id}"}'If the API identifies resources entirely through the request body (e.g., a GraphQL endpoint), the --target URL can be static (no {id}):
--target "https://api.example.com/graphql" \
--body '{"query": "{ user(id: \"{id}\") { name } }"}'Output Formats
All output goes to stdout. Errors go to stderr.
Table (default)
Human-readable terminal table with ANSI color coding per verdict level. Designed for interactive use.
existence output columns:
| Column | Description |
|---|---|
| Oracle | Oracle class (e.g., Existence) |
| Verdict | Confirmed, Likely, Inconclusive, or NotPresent |
| Severity | High, Medium, Low, or — |
| Confidence | Numeric score with impact class in parentheses (e.g., 85 (Medium)), or — when zero |
| Evidence | Primary signal description (e.g., 403 (baseline) vs 404 (probe)) |
When a pattern is matched, additional metadata rows appear below the main row:
| Row | Description |
|---|---|
| Label | Human-readable name for the matched pattern (e.g., Authorization-based differential) |
| Leaks | What the oracle reveals (e.g., Resource existence confirmed to low-privilege callers) |
| RFC Basis | Specification section grounding the behavior (e.g., RFC 9110 §15.5.4) |
Example table output for a 403/404 differential:
+-----------+-----------+-----------+--------------------------+-------------------------------+
| Oracle | Verdict | Severity | Confidence | Evidence |
+===========+===========+===========+==========================+===============================+
| Existence | Confirmed | Medium | 85 (Medium) | 403 (baseline) vs 404 (probe) |
|-----------+-----------+-----------+--------------------------+-------------------------------|
| | | Label | Authorization-based differential |
|-----------+-----------+-----------+--------------------------+-------------------------------|
| | | Leaks | Resource existence confirmed to low-privilege callers |
|-----------+-----------+-----------+--------------------------+-------------------------------|
| | | RFC Basis | RFC 9110 §15.5.4 |
+-----------+-----------+-----------+--------------------------+-------------------------------+scan output columns:
| Column | Description |
|---|---|
| Strategy | Name of the elicitation strategy that generated the probe pair |
| Method | HTTP method used |
| Verdict | Confirmed, Likely, Inconclusive, or NotPresent |
| Severity | High, Medium, Low, or — |
| Confidence | Numeric score with impact class, or — when zero |
| Evidence | Primary signal description |
JSON
Pretty-printed structured JSON following the parlov v1.0.0 schema. Designed for scripting, piping, and downstream tooling.
existence output structure:
{
"schema_version": "1.0.0",
"target_url": "https://api.example.com/users/{id}",
"finding": {
"finding_id": "ee77a4ef7711",
"strategy": {
"id": "existence-cli",
"name": "CLI existence probe",
"method": "GET"
},
"result": {
"oracle_class": "Existence",
"verdict": "Confirmed",
"confidence": 85,
"severity": "Medium",
"impact_class": "Medium"
},
"technique": {
"id": "existence-cli",
"vector": "StatusCodeDiff",
"normative_strength": "Should"
},
"matched_pattern": {
"label": "Authorization-based differential",
"leaks": "Resource existence confirmed to low-privilege callers",
"rfc_basis": "RFC 9110 §15.5.4"
},
"evidence": {
"reasons": [
{
"description": "Authorization-based differential (base +403→85)",
"points": 85,
"dimension": "Confidence"
}
],
"signals": [
{
"kind": "StatusCodeDiff",
"evidence": "403 (baseline) vs 404 (probe)"
}
]
}
}
}scan output structure:
{
"schema_version": "1.0.0",
"target_url": "https://api.example.com/users/{id}",
"findings": [
{
"finding_id": "...",
"strategy": { "id": "...", "name": "...", "method": "..." },
"result": { "oracle_class": "...", "verdict": "...", "confidence": 0, "severity": "..." },
"technique": { "id": "...", "vector": "...", "normative_strength": "..." },
"matched_pattern": { },
"evidence": { "reasons": [], "signals": [] }
}
]
}Key differences between existence and scan JSON:
existencewraps results in a singularfindingobject.scanwraps results in afindingsarray containing zero or more finding objects.- Both share the same
FindingOutputschema per finding.
JSON field reference:
| Field | Type | Description |
|---|---|---|
schema_version | string | Always "1.0.0". |
target_url | string | The --target URL template as provided. |
finding_id | string | 12-character hex fingerprint. Deterministic — same inputs produce the same ID. |
strategy.id | string | Machine-readable strategy identifier. |
strategy.name | string | Human-readable strategy name. |
strategy.method | string | HTTP method used. |
result.oracle_class | string | "Existence", "Authentication", "Timing", "Verification", "TokenValidation", or "State". |
result.verdict | string | "Confirmed", "Likely", "Inconclusive", or "NotPresent". |
result.confidence | integer | 0–100 confidence score. |
result.severity | string | "High", "Medium", "Low", or "None". |
result.impact_class | string or null | Impact class when distinct from severity. Omitted when null. |
technique.id | string | Technique identifier (e.g., "get-403-404"). |
technique.vector | string | Detection vector: "StatusCodeDiff", "CacheProbing", or "ErrorMessageGranularity". |
technique.normative_strength | string | "Must", "Should", "May", or "Unknown". |
matched_pattern.label | string or absent | Pattern name. Omitted when no pattern matched. |
matched_pattern.leaks | string or absent | What the oracle reveals. Omitted when no pattern matched. |
matched_pattern.rfc_basis | string or absent | RFC section. Omitted when no pattern matched. |
evidence.reasons | array | Scoring reasons, each with description, points, and dimension. |
evidence.signals | array | Typed signals, each with kind and evidence. |
SARIF
SARIF v2.1.0 output for CI/CD integration. Compatible with GitHub Advanced Security, GitHub Code Scanning, and other SARIF-consuming platforms.
SARIF-specific behavior:
- Verdict-to-level mapping:
Confirmed→error,Likely→warning,InconclusiveandNotPresent→note. - NotPresent findings are excluded from the
resultsarray entirely. - Rule IDs are technique-based (e.g.,
get-403-404). - Fingerprints: Each result carries a deterministic
oracleFingerprint/v1(12-character hex) and apartialFingerprintsentry for deduplication across runs. - Signals map to
relatedLocationson each result. logicalLocationsis emitted on every result with the HTTP method and target URL:"logicalLocations": [{"kind": "url", "name": "GET https://..."}].security-severityis set toconfidence / 10.0(0.0–10.0 scale) in rule properties.scanruns includetarget_urlinrun.properties.
Example SARIF structure (abbreviated):
{
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "parlov",
"version": "0.6.0",
"rules": [{
"id": "get-403-404",
"name": "ExistenceOracle",
"shortDescription": { "text": "Authorization-based differential" },
"properties": {
"oracle_class": "Existence",
"vector": "StatusCodeDiff",
"security-severity": "8.5"
}
}]
}
},
"results": [{
"ruleId": "get-403-404",
"level": "error",
"message": { "text": "Resource existence confirmed to low-privilege callers" },
"locations": [{
"physicalLocation": {
"artifactLocation": { "uri": "https://api.example.com/users/{id}" }
}
}],
"fingerprints": { "oracleFingerprint/v1": "ee77a4ef7711" },
"relatedLocations": [{
"id": 0,
"message": { "text": "[StatusCodeDiff] 403 (baseline) vs 404 (probe)" }
}],
"properties": {
"oracle_class": "Existence",
"verdict": "Confirmed",
"confidence": 85,
"method": "GET"
}
}]
}]
}Invocation Patterns
Common real-world usage patterns.
Quick existence check
The fastest way to test whether an endpoint leaks resource existence:
parlov existence \
--target "https://api.example.com/users/{id}" \
--baseline-id 1001Uses GET, random probe UUID, table output. If a differential exists, it will appear immediately.
Full safe scan
Run all safe (read-only) strategies against an endpoint:
parlov scan \
--target "https://api.example.com/users/{id}" \
--baseline-id 1001--risk safe is the default. This runs GET, HEAD, and conditional request strategies — no server state is modified.
Escalating risk levels
Start safe, escalate if needed:
# Step 1: safe only
parlov scan --target "https://api.example.com/users/{id}" --baseline-id 42
# Step 2: include POST/PATCH/PUT strategies
parlov scan --target "https://api.example.com/users/{id}" --baseline-id 42 \
--risk method-destructive
# Step 3: include DELETE and resource-exhaustion strategies
parlov scan --target "https://api.example.com/users/{id}" --baseline-id 42 \
--risk operation-destructiveTesting across auth contexts
The same endpoint must be tested from three perspectives. Run parlov once per context:
# Unauthenticated
parlov scan --target "https://api.example.com/documents/{id}" --baseline-id doc-42
# Authenticated, no access (different organization's token)
parlov scan --target "https://api.example.com/documents/{id}" --baseline-id doc-42 \
--header "Authorization: Bearer $OTHER_ORG_TOKEN"
# Authenticated, has access
parlov scan --target "https://api.example.com/documents/{id}" --baseline-id doc-42 \
--header "Authorization: Bearer $OWN_ORG_TOKEN"Body-based APIs
When the identifier is in the request body (common for POST-based lookup APIs, GraphQL, and RPC-style endpoints):
parlov existence \
--target "https://api.example.com/graphql" \
--baseline-id "user-42" \
--method POST \
--header "Content-Type: application/json" \
--body '{"query": "{ user(id: \"{id}\") { email } }"}'JSON output for scripting
Pipe JSON output to jq for extraction:
# Extract verdict from a single probe
parlov existence --format json \
--target "https://api.example.com/users/{id}" \
--baseline-id 1001 \
| jq '.finding.result.verdict'
# Filter scan findings to confirmed only
parlov scan --format json \
--target "https://api.example.com/users/{id}" \
--baseline-id 1001 \
| jq '[.findings[] | select(.result.verdict == "Confirmed")]'SARIF for CI/CD
Generate SARIF output for GitHub Code Scanning:
parlov scan --format sarif \
--target "https://api.example.com/users/{id}" \
--baseline-id 1001 \
> results.sarif
# Upload to GitHub (using the gh CLI)
gh api \
-X POST \
"/repos/{owner}/{repo}/code-scanning/sarifs" \
-f "sarif=@results.sarif" \
-f "ref=refs/heads/main"Targeting specific vectors
Run only cache-probing strategies:
parlov scan \
--target "https://api.example.com/users/{id}" \
--baseline-id 1001 \
--vector cache-probingRun cache-probing up to method-destructive and status-code-diff at safe only:
parlov scan \
--target "https://api.example.com/users/{id}" \
--baseline-id 1001 \
--vector cache-probing:method-destructive \
--vector status-code-diffExit Codes
| Code | Meaning |
|---|---|
0 | Success. Results were produced (regardless of verdict). |
1 | Error. An unrecoverable error occurred (invalid arguments, network failure, serialization error). The error message is printed to stderr. |