parlov_core/lib.rs
1//! Shared types, error types, and oracle class definitions used across all parlov crates.
2//!
3//! This crate is the dependency root of the workspace — it carries no deps on other workspace
4//! crates and is designed to compile fast. Everything in here is pure data: no I/O, no async,
5//! no heavy dependencies.
6
7#![deny(clippy::all)]
8#![warn(clippy::pedantic)]
9#![deny(missing_docs)]
10
11mod serde_helpers;
12
13use bytes::Bytes;
14use http::{HeaderMap, Method, StatusCode};
15use serde::{Deserialize, Serialize};
16use serde_helpers::{
17 bytes_serde, header_map_serde, method_serde, opt_bytes_serde, status_code_serde,
18};
19
20/// A single HTTP interaction: full response surface and wall-clock timing.
21///
22/// Captures everything needed for differential analysis — status, headers, body, and timing —
23/// in one flat structure.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ResponseSurface {
26 /// HTTP status code returned by the server.
27 #[serde(with = "status_code_serde")]
28 pub status: StatusCode,
29 /// Full response header map.
30 #[serde(with = "header_map_serde")]
31 pub headers: HeaderMap,
32 /// Raw response body bytes, serialized as a base64-encoded byte sequence.
33 #[serde(with = "bytes_serde")]
34 pub body: Bytes,
35 /// Wall-clock response time in nanoseconds, measured from first byte sent to last byte
36 /// received.
37 pub timing_ns: u64,
38}
39
40/// A single HTTP request to execute against a target.
41///
42/// The authorization context is expressed entirely through the `headers` field — set an
43/// `Authorization` header for bearer tokens, API keys, or Basic auth. No special-case auth
44/// fields.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ProbeDefinition {
47 /// Fully-qualified target URL including scheme, host, path, and any query parameters.
48 pub url: String,
49 /// HTTP method for the request.
50 #[serde(with = "method_serde")]
51 pub method: Method,
52 /// Request headers, including any authorization context.
53 #[serde(with = "header_map_serde")]
54 pub headers: HeaderMap,
55 /// Request body. `None` for GET, HEAD, DELETE; `Some` for POST, PATCH, PUT.
56 #[serde(with = "opt_bytes_serde")]
57 pub body: Option<Bytes>,
58}
59
60/// Paired response surfaces for differential analysis.
61///
62/// `baseline` holds responses for the control input (e.g. a known-existing resource ID).
63/// `probe` holds responses for the variable input (e.g. a randomly generated nonexistent ID).
64/// Multiple samples per side support statistical analysis for timing oracles.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ProbeSet {
67 /// Responses for the known-valid / control input.
68 pub baseline: Vec<ResponseSurface>,
69 /// Responses for the unknown / suspect input.
70 pub probe: Vec<ResponseSurface>,
71}
72
73/// The oracle class being probed.
74///
75/// Each variant corresponds to a distinct detection strategy and analysis pipeline.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
77#[non_exhaustive]
78pub enum OracleClass {
79 /// Status-code or body differential between an existing and nonexistent resource.
80 Existence,
81}
82
83/// Confidence level of an oracle detection result.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
85pub enum OracleVerdict {
86 /// Signal is unambiguous: differential is consistent, statistically significant, and matches
87 /// a known oracle pattern.
88 Confirmed,
89 /// Signal is present and consistent with an oracle, but evidence is not conclusive (e.g.
90 /// borderline p-value, single sample).
91 Likely,
92 /// Signal is present but too weak or inconsistent to classify.
93 Inconclusive,
94 /// No differential signal detected; the endpoint does not exhibit this oracle.
95 NotPresent,
96}
97
98/// Severity of a confirmed or likely oracle.
99///
100/// `None` on an `OracleResult` when the verdict is `NotPresent` or `Inconclusive`.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
102pub enum Severity {
103 /// Directly actionable: resource existence, valid credentials, or session state is leaked
104 /// to unauthenticated or low-privilege callers.
105 High,
106 /// Leaks internal state but requires additional steps to exploit.
107 Medium,
108 /// Informational: leaks metadata that may assist further enumeration.
109 Low,
110}
111
112/// The result of running an oracle analyzer against a `ProbeSet`.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct OracleResult {
115 /// Which oracle class produced this result.
116 pub class: OracleClass,
117 /// Confidence verdict.
118 pub verdict: OracleVerdict,
119 /// Human-readable descriptions of each signal contributing to the verdict, e.g.
120 /// `"403 (baseline) vs 404 (probe)"`.
121 pub evidence: Vec<String>,
122 /// Severity when the verdict is `Confirmed` or `Likely`; `None` when `NotPresent`.
123 pub severity: Option<Severity>,
124}
125
126/// Errors produced by parlov crates.
127#[derive(Debug, thiserror::Error)]
128#[non_exhaustive]
129pub enum Error {
130 /// HTTP-level error from the probe engine.
131 #[error("http error: {0}")]
132 Http(String),
133 /// Analysis failed due to insufficient or malformed probe data.
134 #[error("analysis error: {0}")]
135 Analysis(String),
136 /// Serialization or deserialization failure.
137 #[error("serialization error: {0}")]
138 Serialization(#[from] serde_json::Error),
139}