Skip to main content

Crate solid_pod_rs

Crate solid_pod_rs 

Source
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

FlagDefaultPurpose
fs-backendonPOSIX filesystem storage.
memory-backendonIn-process HashMap storage (tests/demos).
s3-backendoffAWS S3 / S3-compatible object stores.
oidcoffSolid-OIDC 0.1 + DPoP.
dpop-replay-cacheoffDPoP jti replay cache (pulls oidc).
nip98-schnorroffBIP-340 signature verification for NIP-98.
acl-originoffWAC acl:origin enforcement.
security-primitivesoffSSRF guard + dotfile allowlist.
legacy-notificationsoffsolid-0.1 WebSocket adapter (SolidOS).
config-loaderoffLayered config loader with JSS_* env vars.
webhook-signingoffRFC 9421 Ed25519 webhook signing.
did-nostroffdid:nostr resolver in interop.
rate-limitoffSliding-window LRU rate limiter.
quotaoffPer-pod .quota.json sidecar (atomic writes).

§Module overview

ModuleResponsibility
storageStorage trait + FS / Memory / S3 backends.
ldpResources, containers, content negotiation, PATCH, Prefer.
wacAccess control evaluator + WAC 2.0 conditions framework.
webidWebID profile documents (emits solid:oidcIssuer + CID).
authNIP-98 HTTP authentication.
notificationsWebSocket, Webhook (RFC 9421 signed), legacy adapter.
errorCrate-wide PodError error type.
configLayered configuration schema.
securitySSRF guard, dotfile allowlist, CORS, rate limiter.
quotaPer-pod byte-quota enforcement.
multitenantPodResolver trait; path + subdomain modes.
interop.well-known/solid, WebFinger, NodeInfo, did:nostr.
provisionPod 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

FlagDefaultPurpose
fs-backendonPOSIX filesystem storage.
memory-backendonIn-process HashMap storage (tests/demos).
s3-backendoffAWS S3 / S3-compatible object stores.
oidcoffSolid-OIDC 0.1 + DPoP.
dpop-replay-cacheoffDPoP jti replay cache (pulls oidc).
nip98-schnorroffBIP-340 signature verification for NIP-98.
acl-originoffWAC acl:origin enforcement.
security-primitivesoffSSRF guard + dotfile allowlist.
legacy-notificationsoffsolid-0.1 WebSocket adapter (SolidOS).
config-loaderoffLayered config loader with JSS_* env vars.
webhook-signingoffRFC 9421 Ed25519 webhook signing.
did-nostroffdid:nostr resolver in interop.
rate-limitoffSliding-window LRU rate limiter.
quotaoffPer-pod .quota.json sidecar (atomic writes).

§Modules

ModuleResponsibility
storageStorage trait + FS / Memory / S3 backends.
ldpResources, containers, content negotiation, PATCH, Prefer.
wacAccess control evaluator + WAC 2.0 conditions framework.
webidWebID profile documents (emits solid:oidcIssuer + CID).
authNIP-98 HTTP authentication.
oidcSolid-OIDC 0.1 + DPoP (verified) + JWKS + replay cache.
notificationsWebSocket, Webhook (RFC 9421 signed), legacy adapter.
securitySSRF guard + dotfile allowlist + CORS + rate limiter.
quotaPer-pod .quota.json sidecar with atomic writes.
multitenantPodResolver trait; path + subdomain modes.
configLayered configuration schema.
interop.well-known/solid, WebFinger, NodeInfo 2.1, did:nostr.
provisionPod 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.

CrateLOCParity rowsJSS source refs
solid-pod-rs-activitypub4,453102–108, 131, 169–172src/ap/{index,routes/inbox,routes/outbox,store}.js
solid-pod-rs-git1,68569, 100src/handlers/git.js
solid-pod-rs-idp6,16074–81, 130src/idp/{index,provider,passkey,interactions,credentials}.js
solid-pod-rs-nostr2,17789, 90, 101, 132src/{did/resolver,nostr/relay,auth/did-nostr}.js
solid-pod-rs-didkey1,167153W3C 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=none and HMAC hard-rejected. ath access-token hash binding enforced. jti replay cache under dpop-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 .account are 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 via parse_jsonld_acl_with_limits. Returns PodError::PayloadTooLarge on 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

§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::is_safe_url;
pub use security::resolve_and_check;
pub use security::DotfileAllowlist;
pub use security::DotfileError;
pub use security::DotfilePathError;
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 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::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 multitenant::PathResolver;
pub use multitenant::PodResolver;
pub use multitenant::ResolvedPath;
pub use multitenant::SubdomainResolver;
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;
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;

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.