Expand description
Framework-agnostic Rust library for serving Solid Protocol 0.11 pods.
solid-pod-rs provides LDP resource and container semantics, Web Access
Control (WAC 1.x + 2.0), WebID profile documents, Solid-OIDC 0.1,
NIP-98 HTTP auth, and Solid Notifications 0.2 – all without coupling
to a specific HTTP framework. Wire it into actix-web, axum, hyper, or
anything else; the crate never mounts routes itself.
For a turnkey binary, use the sibling crate
solid-pod-rs-server.
§Feature flags
| Flag | Default | Purpose |
|---|---|---|
core | off | Pure-logic surfaces only — wasm32 / CF Workers. |
std | on | std lib (always; reserved for future no_std). |
tokio-runtime | on | Tokio + tokio-tungstenite + futures-util. |
notifications | on | WebSocketChannel2023 + WebhookChannel2023. |
fs-backend | on | POSIX filesystem storage. |
memory-backend | on | In-process HashMap storage (tests/demos). |
s3-backend | off | AWS S3 / S3-compatible object stores. |
oidc | off | Solid-OIDC 0.1 + DPoP. |
dpop-replay-cache | off | DPoP jti replay cache (pulls oidc). |
nip98-schnorr | off | BIP-340 signature verification for NIP-98. |
acl-origin | off | WAC acl:origin enforcement. |
security-primitives | off | SSRF guard + dotfile allowlist. |
legacy-notifications | off | solid-0.1 WebSocket adapter (SolidOS). |
config-loader | off | Layered config loader with JSS_* env vars. |
webhook-signing | off | RFC 9421 Ed25519 webhook signing. |
did-nostr | off | did:nostr resolver in interop. |
rate-limit | off | Sliding-window LRU rate limiter. |
quota | off | Per-pod .quota.json sidecar (atomic writes). |
core consumers wire the crate via default-features = false, features = ["core"] and get only the pure-logic surfaces (no
tokio, no reqwest, no DNS resolver, no filesystem). See
RELEASE_NOTES.md v0.4.0-alpha.3 for the absorbed surfaces map.
§Module overview
| Module | Responsibility |
|---|---|
storage | Storage trait + FS / Memory / S3 backends. |
ldp | Resources, containers, content negotiation, PATCH, Prefer. |
wac | Access control evaluator + WAC 2.0 conditions framework. |
webid | WebID profile documents (emits solid:oidcIssuer + CID). |
auth | NIP-98 HTTP authentication. |
notifications | WebSocket, Webhook (RFC 9421 signed), legacy adapter. |
error | Crate-wide PodError error type. |
config | Layered configuration schema. |
security | SSRF guard, dotfile allowlist, CORS, rate limiter. |
quota | Per-pod byte-quota enforcement. |
multitenant | PodResolver trait; path + subdomain modes. |
interop | .well-known/solid, WebFinger, NodeInfo, did:nostr. |
provision | Pod bootstrap (WebID + containers + type indexes + ACL). |
§Quick start
use solid_pod_rs::storage::memory::MemoryBackend;
use solid_pod_rs::{Storage, evaluate_access, AccessMode};
use bytes::Bytes;
use std::sync::Arc;
// 1. Create a storage backend.
let store = Arc::new(MemoryBackend::new());
// 2. PUT a resource.
store.put("/hello.txt", Bytes::from("world"), "text/plain").await.unwrap();
// 3. GET it back.
let (body, meta) = store.get("/hello.txt").await.unwrap();
assert_eq!(&body[..], b"world");
assert_eq!(meta.content_type, "text/plain");
// 4. WAC evaluation (no ACL document = deny by default).
let allowed = evaluate_access(None, Some("https://alice.example/profile/card#me"),
"/hello.txt", AccessMode::Read, None);
assert!(!allowed);§Attribution
Rust port of JavaScriptSolidServer. See NOTICE for provenance.
§solid-pod-rs
Framework-agnostic Rust library for serving Solid Protocol 0.11 pods: LDP resources and containers, Web Access Control, WebID, Solid Notifications 0.2, Solid-OIDC 0.1, and NIP-98 HTTP auth.
Parity vs JSS: ~100 % spec-normative (~98 % strict on the full
132-row tracker — see PARITY-CHECKLIST.md).
702 tests pass across the workspace as of Sprint 12 close (2026-05-06).
The library has no opinions about the HTTP runtime; wire it into
actix-web, axum, hyper, or any other server. For a turnkey binary
use the sibling crate solid-pod-rs-server.
[dependencies]
solid-pod-rs = "0.4.0-alpha.1"use solid_pod_rs::storage::fs::FsBackend;
use std::path::PathBuf;
let storage = FsBackend::new(PathBuf::from("./pod-root"));
// Compose with your framework; see examples/embed_in_actix.rs.§Feature flags
| Flag | Default | Purpose |
|---|---|---|
fs-backend | on | POSIX filesystem storage. |
memory-backend | on | In-process HashMap storage (tests/demos). |
s3-backend | off | AWS S3 / S3-compatible object stores. |
oidc | off | Solid-OIDC 0.1 + DPoP. |
dpop-replay-cache | off | DPoP jti replay cache (pulls oidc). |
nip98-schnorr | off | BIP-340 signature verification for NIP-98. |
acl-origin | off | WAC acl:origin enforcement. |
security-primitives | off | SSRF guard + dotfile allowlist. |
legacy-notifications | off | solid-0.1 WebSocket adapter (SolidOS). |
config-loader | off | Layered config loader with JSS_* env vars. |
webhook-signing | off | RFC 9421 Ed25519 webhook signing. |
did-nostr | off | did:nostr resolver in interop. |
rate-limit | off | Sliding-window LRU rate limiter. |
quota | off | Per-pod .quota.json sidecar (atomic writes). |
§Modules
| Module | Responsibility |
|---|---|
storage | Storage trait + FS / Memory / S3 backends. |
ldp | Resources, containers, content negotiation, PATCH, Prefer. |
wac | Access control evaluator + WAC 2.0 conditions framework. |
webid | WebID profile documents (emits solid:oidcIssuer + CID). |
auth | NIP-98 HTTP authentication. |
oidc | Solid-OIDC 0.1 + DPoP (verified) + JWKS + replay cache. |
notifications | WebSocket, Webhook (RFC 9421 signed), legacy adapter. |
security | SSRF guard + dotfile allowlist + CORS + rate limiter. |
quota | Per-pod .quota.json sidecar with atomic writes. |
multitenant | PodResolver trait; path + subdomain modes. |
config | Layered configuration schema. |
interop | .well-known/solid, WebFinger, NodeInfo 2.1, did:nostr. |
provision | Pod bootstrap (WebID + containers + type indexes + ACL). |
§Sibling crate ecosystem
Five sibling crates live in the workspace — all functional and shipping as of Sprint 12. Integrators may depend on them today.
| Crate | LOC | Parity rows | JSS source refs |
|---|---|---|---|
solid-pod-rs-activitypub | 4,453 | 102–108, 131, 169–172 | src/ap/{index,routes/inbox,routes/outbox,store}.js |
solid-pod-rs-git | 1,685 | 69, 100 | src/handlers/git.js |
solid-pod-rs-idp | 6,160 | 74–81, 130 | src/idp/{index,provider,passkey,interactions,credentials}.js |
solid-pod-rs-nostr | 2,177 | 89, 90, 101, 132 | src/{did/resolver,nostr/relay,auth/did-nostr}.js |
solid-pod-rs-didkey | 1,167 | 153 | W3C did:key spec + LWS 1.0 SSI |
The did:nostr resolver shipped in Sprint 6 lives inside the core
library (interop::did_nostr under did-nostr) as well as the
solid-pod-rs-nostr crate, so the Tier 1 + Tier 3 DID flow is
available either way.
§WAC inheritance model
flowchart TD
REQ["Request for<br/>/pod/notes/2024/entry.ttl"] --> Q1{".acl sidecar<br/>at entry.ttl.acl?"}
Q1 -->|found| EVAL["Evaluate ACL rules<br/>against AuthContext"]
Q1 -->|not found| Q2{".acl at<br/>/pod/notes/2024/.acl?"}
Q2 -->|"found (acl:default)"| EVAL
Q2 -->|not found| Q3{".acl at<br/>/pod/notes/.acl?"}
Q3 -->|"found (acl:default)"| EVAL
Q3 -->|not found| Q4{".acl at<br/>/pod/.acl?"}
Q4 -->|"found (acl:default)"| EVAL
Q4 -->|not found| DENY["DENY<br/>(no ACL = no access)"]
EVAL --> CHK{"Agent match?<br/>acl:agent / agentClass<br/>/ agentGroup"}
CHK -->|"matched + mode ok"| ALLOW["ALLOW<br/>+ WAC-Allow header"]
CHK -->|"no match"| DENY2["DENY<br/>+ WAC-Allow header"]
style REQ fill:#4a90d9,stroke:#2c5f8a,color:#fff
style ALLOW fill:#2ecc71,stroke:#1a9850,color:#fff
style DENY fill:#e74c3c,stroke:#c0392b,color:#fff
style DENY2 fill:#e74c3c,stroke:#c0392b,color:#fff
style EVAL fill:#9b59b6,stroke:#7d3c98,color:#fff§Security posture
- DPoP signature verification against the proof’s embedded JWK
(RFC 9449 §4.3), with an algorithm allowlist (
ES256/ES384,RS256/RS384/RS512,PS256/PS384/PS512,EdDSA);alg=noneand HMAC hard-rejected.athaccess-token hash binding enforced.jtireplay cache underdpop-replay-cache. - SSRF guard — RFC 1918, loopback, link-local, and cloud metadata endpoints are rejected on every outbound fetch (JWKS discovery, webhook delivery, did:nostr resolution). DNS-rebinding is closed by pinning the resolved IP on the per-call reqwest client.
- Dotfile allowlist — only
.acl,.meta,.well-known,.quota.json, and.accountare served. All other dotfiles return 404 regardless of storage-layer presence. - RFC 7638 canonical JWK thumbprints — replaces the previous hand-rolled JSON template; verified byte-for-byte against the spec’s appendix-A vector.
- WAC parser bounds — 1 MiB Turtle input cap via
parse_turtle_acl_with_limit(JSS_MAX_ACL_BYTES); 32-level JSON-LD depth cap viaparse_jsonld_acl_with_limits. ReturnsPodError::PayloadTooLargeon oversized input (CWE-400, Sprint 12). - Atomic quota writes — temp-file + rename so concurrent writers
cannot observe a torn
.quota.json. - RFC 9421 webhook signing — Ed25519 over
@method,@target-uri,content-type,content-digest(RFC 9530),date,x-solid-notification-id.
See SECURITY.md for disclosure policy and a full
cryptographic verification matrix.
§Documentation
- Workspace README:
../../README.md - Diátaxis docs:
docs/ - Agent integration guide:
docs/reference/agent-integration-guide.md - Parity vs JSS:
PARITY-CHECKLIST.md - Gap analysis:
GAP-ANALYSIS.md - Changelog:
CHANGELOG.md
§Licence
AGPL-3.0-only — see ../../LICENSE and
NOTICE.
Re-exports§
pub use auth::nip98::Nip98Verifier;pub use auth::self_signed::CidVerifier;pub use auth::self_signed::ProofEnvelope;pub use auth::self_signed::SelfSignedError;pub use auth::self_signed::SelfSignedVerifier;pub use auth::self_signed::VerifiedSubject;pub use error::PodError;pub use metrics::SecurityMetrics;pub use security::is_path_allowed;pub use security::DotfileAllowlist;pub use security::DotfileError;pub use security::DotfilePathError;pub use wac::check_origin;pub use wac::evaluate_access;pub use wac::evaluate_access_with_groups;pub use wac::extract_origin_patterns;pub use wac::method_to_mode;pub use wac::mode_name;pub use wac::parse_turtle_acl;pub use wac::serialize_turtle_acl;pub use wac::wac_allow_header;pub use wac::AccessMode;pub use wac::AclDocument;pub use wac::GroupMembership;pub use wac::Origin;pub use wac::OriginDecision;pub use wac::OriginPattern;pub use wac::StaticGroupMembership;pub use ldp::apply_json_patch;pub use ldp::apply_n3_patch;pub use ldp::apply_patch_to_absent;pub use ldp::apply_sparql_patch;pub use ldp::cache_control_for;pub use ldp::evaluate_preconditions;pub use ldp::is_rdf_content_type;pub use ldp::link_headers;pub use ldp::negotiate_format;pub use ldp::not_found_headers;pub use ldp::options_for;pub use ldp::parse_range_header;pub use ldp::parse_range_header_v2;pub use ldp::patch_dialect_from_mime;pub use ldp::server_managed_triples;pub use ldp::slice_range;pub use ldp::vary_header;pub use ldp::ByteRange;pub use ldp::ConditionalOutcome;pub use ldp::ContainerRepresentation;pub use ldp::Graph;pub use ldp::OptionsResponse;pub use ldp::PatchCreateOutcome;pub use ldp::PatchDialect;pub use ldp::PatchOutcome;pub use ldp::PreferHeader;pub use ldp::RangeOutcome;pub use ldp::RdfFormat;pub use ldp::Term;pub use ldp::Triple;pub use ldp::ACCEPT_PATCH;pub use ldp::ACCEPT_POST;pub use ldp::CACHE_CONTROL_RDF;pub use interop::dev_session;pub use interop::nip05_document;pub use interop::verify_nip05;pub use interop::webfinger_response;pub use interop::well_known_solid;pub use interop::DevSession;pub use interop::Nip05Document;pub use interop::SolidWellKnown;pub use interop::WebFingerJrd;pub use interop::WebFingerLink;pub use multitenant::PathResolver;pub use multitenant::PodResolver;pub use multitenant::ResolvedPath;pub use multitenant::SubdomainResolver;pub use webid::extract_oidc_issuer;pub use webid::generate_webid_html;pub use webid::generate_webid_html_with_issuer;pub use webid::validate_webid_html;pub use security::is_safe_url;pub use security::resolve_and_check;pub use security::IpClass;pub use security::SsrfError;pub use security::SsrfPolicy;pub use storage::ResourceMeta;pub use storage::Storage;pub use storage::StorageEvent;pub use provision::check_admin_override;pub use provision::provision_pod;pub use provision::AdminOverride;pub use provision::ProvisionOutcome;pub use provision::ProvisionPlan;pub use provision::QuotaTracker;pub use quota::QuotaExceeded;pub use quota::QuotaPolicy;pub use quota::QuotaUsage;
Modules§
- auth
- Authentication modules.
- config
- JSS-compatible server config
- error
- Crate-wide error type.
- interop
- Interop / discovery helpers.
- ldp
- Linked Data Platform (LDP) resource and container semantics.
- metrics
- Minimal metrics sink for security primitives.
- multitenant
- Pod resolution from request host (Sprint 7 §6.3, ADR-057).
- notifications
- Solid Notifications Protocol (0.2) — Phase 2 implementation.
- provision
- Pod provisioning — seeded containers, WebID + account scaffolding, admin override, quota enforcement.
- quota
- Pod-level quota policy (Sprint 7 §6.4, ADR-057).
- security
- Security primitives (Sprint 4 / F1 + F2)
- storage
- Storage abstraction for Solid pods.
- wac
- Web Access Control evaluator.
- webid
- WebID profile document generation and validation.