Skip to main content

solid_pod_rs/security/
mod.rs

1//! # Security primitives (Sprint 4 / F1 + F2)
2//!
3//! Two narrow, orthogonal, library-level security controls promoted
4//! from the HTTP binder into `solid-pod-rs` so every consumer inherits
5//! them uniformly. Closes `GAP-ANALYSIS.md` §H rank 1 and
6//! `PARITY-CHECKLIST.md` rows 114 (SSRF guard) and 115 (dotfile
7//! allowlist). Upstream parity with `JavaScriptSolidServer/src/utils/
8//! ssrf.js:15-157` and `JavaScriptSolidServer/src/server.js:265-281`.
9//!
10//! Design context: `docs/design/jss-parity/01-security-primitives-context.md`.
11//!
12//! ## Aggregates
13//!
14//! - [`SsrfPolicy`] — outbound URL validator. Classifies the resolved IP
15//!   of a target URL and enforces operator-configured
16//!   block/allow/deny lists. Use [`SsrfPolicy::resolve_and_check`]
17//!   before every server-side `fetch`.
18//! - [`DotfileAllowlist`] — inbound path filter. Rejects any path whose
19//!   components start with `.` unless explicitly allowlisted.
20//!   Default allowlist mirrors JSS (`.acl`, `.meta`).
21//!
22//! ## Integration points
23//!
24//! The primitives define the API surface; call-site wiring lands in
25//! later Sprint 4 features (F7 library-server split). Required hooks
26//! per DDD:
27//!
28//! | Caller                         | Trigger               | Primitive                    | Sprint-4 ticket |
29//! |--------------------------------|-----------------------|------------------------------|-----------------|
30//! | LDP handler (pre-GET)          | inbound request       | `DotfileAllowlist::is_allowed` → 403 on deny | F7 |
31//! | LDP handler (pre-PUT/POST/PATCH) | inbound write       | `DotfileAllowlist::is_allowed` → 403 on deny | F7 |
32//! | OIDC JWKS fetcher              | `fetch_jwks`          | `SsrfPolicy::resolve_and_check` → 400 on deny | F5 |
33//! | Webhook delivery worker        | subscription + dispatch | `SsrfPolicy::resolve_and_check` (re-resolve per dispatch — DNS rebinding guard) | F3 |
34//!
35//! ## DNS-rebinding resistance
36//!
37//! [`SsrfPolicy::resolve_and_check`] returns the resolved `IpAddr`.
38//! Callers MUST pass that IP to the subsequent socket connect (for
39//! `reqwest`, via the `resolve` override) so the check and the
40//! connection target the same endpoint. Re-resolving at request time
41//! prevents stale cache bypasses.
42//!
43//! ## Configuration
44//!
45//! All runtime policy is env-driven; see [`SsrfPolicy::from_env`] and
46//! [`DotfileAllowlist::from_env`]. Defaults are fail-safe: SSRF denies
47//! all private/loopback/link-local space; dotfile allowlist permits
48//! only `.acl` and `.meta`.
49
50// `dotfile` and `cors` are pure-logic primitives — always compiled.
51pub mod cors;
52pub mod dotfile;
53
54// `rate_limit` is async-trait based but uses no tokio internals; the
55// trait + decision types compile under `core`. The reference
56// `LruRateLimiter` impl is gated separately.
57pub mod rate_limit;
58
59// `ssrf` reaches for `tokio::net::lookup_host` for DNS resolution;
60// keep it on the tokio-runtime path only.
61#[cfg(feature = "tokio-runtime")]
62pub mod ssrf;
63
64pub use cors::{AllowedOrigins, CorsPolicy};
65pub use dotfile::{is_path_allowed, DotfileAllowlist, DotfileError, DotfilePathError};
66pub use rate_limit::{RateLimitDecision, RateLimitKey, RateLimitSubject, RateLimiter};
67#[cfg(feature = "tokio-runtime")]
68pub use ssrf::{is_safe_url, resolve_and_check, IpClass, SsrfError, SsrfPolicy};
69
70#[cfg(feature = "rate-limit")]
71pub use rate_limit::LruRateLimiter;