Skip to content

API reference

The public surface of tower_guard_rs 1.0.0. The full crate documentation is also in src/lib.rs (build it with cargo doc --open).

Middleware

GuardLayer

The tower layer entry point. It implements tower::Layer over http::Request<B>; wrap any Service<Request<B>> with Layer::layer:

use tower::Layer;

let layer = tower_guard_rs::GuardLayer::new(tower_guard_rs::default_config());
let service = layer.layer(my_service);

Constructors and builders:

Method Description
GuardLayer::new(config: DetectConfig) Build a layer from an engine DetectConfig. The body buffering cap starts at config.max_full_scan_bytes
GuardLayer::with_defaults() Build a layer with default_config()
.with_body_cap(body_cap: usize) Replace the body buffering cap, in bytes. A body larger than the cap is rejected with 413. A cap of 0 rejects every request that carries a non-empty body
.with_ip_gate(ip_gate: IpGateConfig) Install the global IP gate (see below)

GuardClientIp

The extension type the IP gate reads the client IP from:

pub struct GuardClientIp(pub IpAddr);

The tower Service surface is framework-neutral, so nothing inserts it automatically. axum applications map ConnectInfo<SocketAddr> into it (axum-guard-rs ships client_ip_layer() for exactly that); a proxy frontend can insert its resolved client IP instead. A request without the extension is not attributed: the gate does not run, and detection still screens the request.

The IP gate: IpGateConfig

Built with IpGateConfig::new(whitelist, blacklist, exempt_ips), which fails closed on an invalid entry (IpGateError names the list and the entry):

use tower_guard_rs::IpGateConfig;

let gate = IpGateConfig::new(
    [] as [&str; 0],
    ["203.0.113.9"],
    ["198.51.100.7", "198.51.100.16/28"],
)
.expect("valid lists");
let layer = tower_guard_rs::GuardLayer::new(tower_guard_rs::default_config())
    .with_ip_gate(gate);

Evaluation order mirrors the reference engine: with a non-empty whitelist, an IP matching neither the whitelist nor exempt_ips is denied; otherwise a blacklist hit is denied. Both denials answer 403 Forbidden with Forbidden before body buffering. A passed request gets an IpGateDecision { is_whitelisted, is_exempt } inserted into its request extensions so downstream handlers can read the skip state.

exempt_ips vs whitelist. exempt_ips is noise reduction for known-friendly automation (monitoring probes, VPN egress, a partner's server), not immunity: it sets the same skip state a whitelist match sets but never adds a deny path, and it never opens the whitelist gate. The blacklist, route rules, and detection still apply to exempt IPs - an attack payload from an exempt IP is still 400 Suspicious activity detected. The Rust family ships no rate limiter, user-agent filter, cloud-provider blocker, or violation counter yet; a stage that lands later must skip exactly what the reference skips for a whitelist match (is_whitelisted || is_exempt) and never skip detection.

GuardService

The tower::Service produced by GuardLayer. It screens a http::Request<B> by running one engine call per request view and, on a clean pass, hands the inner service a rebuilt request whose body is byte-identical to what the client sent (bodies are buffered under the cap and rebuilt via B: From<Bytes>). The inner service must be Clone + Send (tower's standard Buffer-style sharing).

GuardBody

The response body type emitted by GuardService, nameable because it appears in the service's Response association:

pub enum GuardBody<B> {
    Passthrough(B),
    Generated(Full<Bytes>),
}

Either the inner service's response body, forwarded untouched (Passthrough), or a Guard-generated plain-text body for a short-circuited response (400, 403, 413, 429, or 500, Generated). It implements http_body::Body.

BoxError

Box<dyn Error + Send + Sync>, the boxed error type used by GuardBody. Identical to tower::BoxError; redefined so the body type does not force a tower re-export onto downstream type signatures.

Configuration

default_config()

Returns the reference default DetectConfig:

Knob Value
max_content_length 10_000
max_full_scan_bytes 262_144
preserve_attack_patterns true
semantic_threshold 0.7
threat_score_threshold 1.0

DetectConfig

Re-exported from guard_core_engine::detect. Fields:

Field Type Meaning
max_content_length usize Semantic budget and truncation budget
max_full_scan_bytes usize Preprocessor full-scan cap (also the default body cap)
preserve_attack_patterns bool Keep attack patterns in the processed view
semantic_threshold f64 Semantic analysis threshold
threat_score_threshold f64 Threat score threshold for a verdict

Behavior

What it inspects

One engine call per request view:

Request part Engine context Notes
Path url_path Skipped for /
Query string query_param Skipped when empty
Header values header Skips sec-* and the negotiation/routing headers
Body request_body Buffered first, capped

The HTTP method is not scanned.

Responses

Situation Status Body
The IP gate denies the client IP 403 Forbidden Forbidden
Engine flags a view 400 Bad Request Suspicious activity detected
Body exceeds the cap 413 Payload Too Large Payload too large
Body read error or engine panic 500 Internal Server Error Security check failed

The adapter is fail-secure: any failure to complete the security check answers 500, never an uninspected passthrough. Engine panics are caught with catch_unwind (note that panic = "abort" in a release profile disables that recovery).

Constants

Re-exported refusal message bodies:

Constant Value
BLOCKED_MESSAGE "Suspicious activity detected"
FORBIDDEN_MESSAGE "Forbidden"
OVERSIZE_MESSAGE "Payload too large"
FAILURE_MESSAGE "Security check failed"

Engine re-exports

DetectConfig, DetectVerdict, and Threat are re-exported from guard_core_engine::detect. A DetectVerdict carries is_threat, a threat_score, and the list of Threat findings (regex or semantic).

IpGateConfig, IpGateDecision, IpGateDenial, IpGateError, and IpGateVerdict are re-exported from guard_core_engine::ip_gate.