parlov-elicit
Elicitation engine for parlov. Given a target endpoint and operator context, generates a plan of ProbeSpecs designed to trigger specific server-side differentials that reveal resource existence.
Overview
The engine codifies the elicitation playbook as 39 composable strategies across four detection vectors. Each strategy targets a different layer of the HTTP pipeline — content negotiation, conditional headers, cache validation, auth, payload validation, rate limiting — and produces probe definitions with Technique metadata that the binary feeds into its execution loops.
A second generate_dag_chained_plan(ctx, exchanges, registry) path walks a typed Producer/Consumer DAG to emit Phase-2 chained specs whose construction depends on Phase-1 observations (e.g. follow a baseline Location redirect, replay an ETag validator, substitute extracted resource IDs). Seven chain families ship in default_chain_registry: redirect-location (B1), ETag validators (B2), Content-Range size (B3), resource-ID (B4), Problem-Details (B5), Content-Type negotiation (C7), and auth-challenge informed (C8). Phase-2 specs carry ChainProvenance so each derived finding can be traced back to the harvested signal that produced it.
Usage
use parlov_elicit::{generate_plan, RiskLevel, ScanContext};
use http::HeaderMap;
let ctx = ScanContext {
target: "https://api.example.com/users/{id}".to_string(),
baseline_id: "1001".to_string(),
probe_id: "9999".to_string(),
headers: HeaderMap::new(),
max_risk: RiskLevel::Safe,
known_duplicate: None,
state_field: None,
alt_credential: None,
};
let plan = generate_plan(&ctx);
Strategies
Status Code Diff (Vector::StatusCodeDiff)
| # |
ID |
Risk |
Methods |
Prereq |
| 1 |
accept-elicit |
Safe |
GET, HEAD |
— |
| 2 |
if-none-match-elicit |
Safe |
GET, HEAD |
— |
| 3 |
if-match-read-elicit |
Safe |
GET, HEAD |
— |
| 4 |
trailing-slash-elicit |
Safe |
GET, HEAD |
— |
| 5 |
case-normalize-elicit |
Safe |
GET, HEAD |
— |
| 6 |
auth-strip-elicit |
Safe |
GET, HEAD |
Authorization header |
| 7 |
low-privilege-elicit |
Safe |
GET, HEAD |
Authorization header |
| 8 |
scope-manipulation-elicit |
Safe |
GET, HEAD |
alt_credential |
| 9 |
rate-limit-headers-elicit |
Safe |
GET, HEAD |
— |
| 10 |
content-type-elicit |
MethodDestructive |
PUT, PATCH |
— |
| 11 |
if-match-elicit |
MethodDestructive |
PUT, PATCH, DELETE |
— |
| 12 |
empty-body-elicit |
MethodDestructive |
POST, PUT, PATCH |
— |
| 13 |
state-transition-elicit |
MethodDestructive |
PATCH, PUT |
state_field |
| 14 |
uniqueness-elicit |
OperationDestructive |
POST, PUT |
known_duplicate |
| 15 |
dependency-delete-elicit |
OperationDestructive |
DELETE |
— |
| 16 |
rate-limit-burst-elicit |
OperationDestructive |
GET, HEAD |
— |
Cache Probing (Vector::CacheProbing)
| # |
ID |
Risk |
Methods |
Prereq |
| 17 |
cp-if-none-match |
Safe |
GET, HEAD |
— |
| 18 |
cp-if-modified-since |
Safe |
GET, HEAD |
— |
| 19 |
cp-if-match |
Safe |
GET, HEAD |
— |
| 20 |
cp-if-unmodified-since |
Safe |
GET, HEAD |
— |
| 21 |
cp-range-satisfiable |
Safe |
GET |
— |
| 22 |
cp-range-unsatisfiable |
Safe |
GET |
— |
| 23 |
cp-if-range |
Safe |
GET |
— |
| 24 |
cp-accept |
Safe |
GET, HEAD |
— |
Error Message Granularity (Vector::ErrorMessageGranularity)
| # |
ID |
Risk |
Methods |
Prereq |
| 25 |
emg-bola |
Safe |
GET |
— |
| 26 |
emg-query-validation |
Safe |
GET |
— |
| 27 |
emg-app-vs-server-404 |
Safe |
GET |
— |
| 28 |
emg-schema-validation-patch |
MethodDestructive |
PATCH |
— |
| 29 |
emg-schema-validation-put |
MethodDestructive |
PUT |
— |
| 30 |
emg-state-conflict |
MethodDestructive |
POST |
— |
| 31 |
emg-fk-violation |
MethodDestructive |
DELETE |
— |
Redirect Diff (Vector::RedirectDiff)
| # |
ID |
Risk |
Methods |
Prereq |
| 32 |
rd-slash-append |
Safe |
GET, HEAD |
— |
| 33 |
rd-slash-strip |
Safe |
GET, HEAD |
— |
| 34 |
rd-case-variation |
Safe |
GET, HEAD |
— |
| 35 |
rd-double-slash |
Safe |
GET, HEAD |
— |
| 36 |
rd-percent-encoding |
Safe |
GET, HEAD |
— |
| 37 |
rd-protocol-upgrade |
Safe |
GET, HEAD |
— |
| 38 |
rd-post-to-303 |
MethodDestructive |
POST |
— |
| 39 |
rd-put-to-303 |
MethodDestructive |
PUT |
— |
Design
- Pure computation — no I/O, no async. The binary owns the async boundary.
- Strategy as trait — adding a strategy is one file + one registry line.
- Four detection vectors —
StatusCodeDiff (status code differentials), CacheProbing (cache-conditional header responses), ErrorMessageGranularity (response body differentials), and RedirectDiff (3xx vs non-3xx redirect differentials). Strategies are organized by vector under existence/status_code_diff/, existence/cache_probing/, existence/error_message_granularity/, and existence/redirect_diff/.
Technique metadata — every ProbeSpec carries technique context (id, name, vector, normative strength) that flows through execution into analysis. Signal extraction is unconditional.
ProbeSpec variants drive dispatch in the binary: Pair → adaptive loop, Burst → volume loop, HeaderDiff → single-request header comparison.
RiskLevel uses Ord — generate_plan filters with risk() <= ctx.max_risk.