Skip to main content

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}