Skip to main content

ruso_runtime/
contract.rs

1//! Shared types embedded in Ruso bytecode (constant pools and probe specs).
2//!
3//! `ruso-script` parses source into an AST that uses these types for matchers,
4//! bodies, and metadata so compiled output matches what `ruso-runtime` executes.
5
6/// Finding severity, in ascending order of urgency (`Info` is the catch-all
7/// used when a check declares no `severity`).
8#[derive(Debug, Clone, PartialEq)]
9pub enum Severity {
10    /// Low impact.
11    Low,
12    /// Moderate impact.
13    Medium,
14    /// High impact.
15    High,
16    /// Critical impact — typically remote code execution or full compromise.
17    Critical,
18    /// Informational; no direct security impact. The default when unset.
19    Info,
20}
21
22impl Severity {
23    pub fn as_str(&self) -> &'static str {
24        match self {
25            Self::Low => "low",
26            Self::Medium => "medium",
27            Self::High => "high",
28            Self::Critical => "critical",
29            Self::Info => "info",
30        }
31    }
32}
33
34/// HTTP request method for an `http` probe.
35#[derive(Debug, Clone, PartialEq)]
36pub enum HttpMethod {
37    /// `GET`
38    Get,
39    /// `POST`
40    Post,
41    /// `PUT`
42    Put,
43    /// `PATCH`
44    Patch,
45    /// `DELETE`
46    Delete,
47    /// `HEAD`
48    Head,
49    /// `OPTIONS`
50    Options,
51}
52
53/// A structured request body (`data { … }` / `json { … }`): ordered key/value
54/// pairs. Order is preserved so serialization is deterministic.
55#[derive(Debug, Clone, PartialEq)]
56pub struct ObjectBody {
57    /// Key/value pairs in source order.
58    pub pairs: Vec<(String, BodyValue)>,
59}
60
61/// A value inside an [`ObjectBody`] or multipart body.
62#[derive(Debug, Clone, PartialEq)]
63pub enum BodyValue {
64    /// A literal string.
65    String(String),
66    /// A string containing `{{ var }}` placeholders to interpolate at runtime.
67    Interpolation(String),
68    /// A nested object.
69    Object(ObjectBody),
70    /// Hex-encoded raw bytes.
71    Bytes(String),
72    /// A multipart part.
73    Part(InlinePart),
74}
75
76/// One part of a multipart request body.
77#[derive(Debug, Clone, PartialEq)]
78pub struct InlinePart {
79    /// Optional `filename` for a file part.
80    pub filename: Option<String>,
81    /// The part's content.
82    pub body: InlinePartBody,
83}
84
85/// The content of an [`InlinePart`].
86#[derive(Debug, Clone, PartialEq)]
87pub enum InlinePartBody {
88    /// UTF-8 text.
89    Text(String),
90    /// Hex-encoded raw bytes.
91    Bytes(String),
92}
93
94/// A response field selector: which probe (`target`) and which part of its
95/// response (`kind`) a matcher or evidence rule reads.
96#[derive(Debug, Clone, PartialEq)]
97pub struct QualifiedField {
98    /// The probe name the field belongs to (e.g. `home`).
99    pub target: String,
100    /// Which part of the response to read.
101    pub kind: FieldKind,
102}
103
104/// Which part of a probe response a matcher reads.
105#[derive(Debug, Clone, PartialEq)]
106pub enum FieldKind {
107    /// HTTP status code.
108    Status,
109    /// HTTP response body.
110    Body,
111    /// A named HTTP response header.
112    Header(String),
113    /// HTTP round-trip time.
114    ResponseTime,
115    /// HTTP response body size in bytes.
116    ResponseSize,
117    /// Resolver answers (`dns` without `port` / `payload`).
118    Answer,
119    /// Raw probe bytes (tcp / udp / wire dns). Alias: `banner` in scripts.
120    Response,
121    Banner,
122}
123
124/// A single matcher: a response [field](QualifiedField) tested against a
125/// [predicate](MatchPredicate).
126#[derive(Debug, Clone, PartialEq)]
127pub struct QualifiedMatch {
128    /// The response field to read.
129    pub field: QualifiedField,
130    /// The condition the field must satisfy.
131    pub predicate: MatchPredicate,
132}
133
134/// The condition a [`QualifiedField`] is tested against.
135#[derive(Debug, Clone, PartialEq)]
136pub enum MatchPredicate {
137    /// Numeric/string/duration comparison (`==`, `!=`, `<`, …).
138    Compare {
139        /// The comparison operator.
140        op: CmpOp,
141        /// The right-hand value.
142        value: CmpValue,
143    },
144    /// Substring is present.
145    Contains(String),
146    /// Substring is absent.
147    NotContains(String),
148    /// Rust-syntax regular expression matches.
149    Regex(String),
150}
151
152/// Where an `extract` pulls a value from (HTTP only).
153#[derive(Debug, Clone, PartialEq)]
154pub enum ExtractSource {
155    /// From the response body, optionally via a capture-group regex.
156    Body {
157        /// Probe name.
158        target: String,
159        /// Optional regex; capture group 1 (or the whole match) is extracted.
160        regex: Option<String>,
161    },
162    /// From a named response header.
163    Header {
164        /// Probe name.
165        target: String,
166        /// Header name.
167        name: String,
168    },
169}
170
171/// A proof string attached to a finding. Every rule names an explicit source
172/// (`.body`, `.response`, or `.header "<name>"`); an optional `regex` extracts
173/// capture group 1 (or the whole match) from that source instead of taking it
174/// whole.
175#[derive(Debug, Clone, PartialEq)]
176pub enum EvidenceKind {
177    /// HTTP probe response body (`p.body` / `p.body regex '…'`).
178    Body {
179        /// Probe name.
180        target: String,
181        /// Optional regex; `None` takes the whole (truncated) body.
182        pattern: Option<String>,
183    },
184    /// Probe response payload — HTTP body, DNS answers, or socket data
185    /// (`p.response` / `p.response regex '…'`).
186    Response {
187        /// Probe name.
188        target: String,
189        /// Optional regex; `None` takes the whole (truncated) payload.
190        pattern: Option<String>,
191    },
192    /// A named HTTP response header value (`p.header "X" ` / `p.header "X" regex '…'`).
193    Header {
194        /// Probe name.
195        target: String,
196        /// Header name (case-insensitive).
197        name: String,
198        /// Optional regex; `None` takes the whole header value.
199        pattern: Option<String>,
200    },
201}
202
203/// A comparison operator used by [`MatchPredicate::Compare`].
204#[derive(Debug, Clone, Copy, PartialEq)]
205pub enum CmpOp {
206    /// `==`
207    Eq,
208    /// `!=`
209    Ne,
210    /// `<`
211    Lt,
212    /// `>`
213    Gt,
214    /// `<=`
215    Le,
216    /// `>=`
217    Ge,
218}
219
220/// The right-hand operand of a [`MatchPredicate::Compare`].
221#[derive(Debug, Clone, PartialEq)]
222pub enum CmpValue {
223    /// A numeric literal (e.g. a status code or size).
224    Number(u64),
225    /// A string literal.
226    String(String),
227    /// A duration literal (e.g. `500ms`), compared against `response_time`.
228    Duration(String),
229}