A Rust-native Solid Pod server. LDP resources and containers, Web Access Control, WebID profiles, Solid Notifications 0.2, Solid-OIDC, and NIP-98 HTTP authentication — delivered as a framework-agnostic library crate and a drop-in server binary.
solid-pod-rs is a Rust implementation of the server side of the Solid Protocol. It ships the full set of protocol primitives — LDP resources and containers, Web Access Control (WAC), WebID profile documents, Solid Notifications 0.2, Solid-OIDC 0.1, and NIP-98 HTTP authentication — as a framework-agnostic library crate. Consumers wire the library into actix-web, axum, hyper, or any other HTTP runtime; the crate has no opinions about how requests reach it.
Operators who want a turnkey deployment use the sibling binary crate
solid-pod-rs-server: a thin actix-web shell over the library,
wired to a JSS-compatible layered configuration loader and a CLI
that reads the same JSS_* environment variables as the reference
JavaScript server. One cargo install command is sufficient to run
a conforming pod.
The target audience is Rust developers building sovereign-data applications, Solid ecosystem implementers who want a native backend without a Node.js runtime dependency, and operators porting Solid deployments to compiled-language environments — edge runtimes, embedded servers, single-binary IoT devices. Feature flags keep the dependency surface tight: a minimal NIP-98-only build fits in under 200 KB of transitive deps; a full OIDC + S3 + notifications build stays under 40 MB.
cargo install solid-pod-rs-server
# Minimal config — one JSON file.
cat > config.json <<'EOF'
{
"server": { "host": "127.0.0.1", "port": 3000 },
"storage": { "kind": "fs", "root": "./pod-root" },
"auth": { "nip98": { "enabled": true } }
}
EOF
solid-pod-rs-server --config config.json# Round-trip a resource.
curl -i -X PUT http://127.0.0.1:3000/notes/hello.ttl \
-H 'Content-Type: text/turtle' \
--data-binary '<#> <http://example.org/says> "Hello, Solid".'
curl -i http://127.0.0.1:3000/notes/hello.ttl
# 200 OK
# ETag: "sha256-..."
# Link: <.acl>; rel="acl", <http://www.w3.org/ns/ldp#Resource>; rel="type"All configuration keys accept either a JSON file entry or a JSS_*
environment variable. See
docs/reference/env-vars.md
for the full list.
[dependencies]
solid-pod-rs = { version = "0.4.0-alpha.1", features = ["fs-backend", "oidc"] }use solid_pod_rs::{storage::FsBackend, wac::evaluate_access, ldp};
use std::path::PathBuf;
let storage = FsBackend::new(PathBuf::from("./pod-root"));
// Wire your HTTP framework of choice; see examples/embed_in_actix.rs.| Feature | Status | Spec clause | Module | Notes |
|---|---|---|---|---|
| LDP Basic Containers | present | Solid Protocol §5.3 | ldp |
ldp:contains direct children only. |
| LDP Resource CRUD | present | Solid Protocol §5.2 | ldp, storage |
Strong SHA-256 ETags. |
| Content negotiation (Turtle, JSON-LD, N-Triples) | present | §5.2.2 | ldp::negotiate_format |
RDF/XML deferred. |
| Conditional requests (If-Match, If-None-Match) | present | RFC 7232 | ldp::evaluate_preconditions |
304 / 412 outcomes. |
| Range requests | present | RFC 7233 | ldp::parse_range_header |
Single-range only. |
| PATCH (N3 Patch, SPARQL-Update, JSON Patch) | present | Solid Protocol §5.2, RFC 6902 | ldp::apply_*_patch |
JSON Patch is a solid-pod-rs extension. |
Prefer header dispatch |
present | LDP §4.2.2, RFC 7240 | ldp::PreferHeader |
Multi-include directives supported. |
| WAC evaluator | present | WAC spec §3–§4 | wac |
Deny-by-default; Read / Write / Append / Control. |
WAC inheritance via acl:default |
present | WAC §4.2 | wac::resolve_applicable_acl |
Nearest ancestor wins. |
WAC acl:origin enforcement |
present | WAC §4.3 | wac::origin |
Feature acl-origin. |
| Turtle ACL parsing | present | Solid Protocol §6 | wac::parse_turtle_acl |
JSON-LD fallback preserved. |
| WebID profile documents | present | WebID spec §2 | webid |
Emits solid:oidcIssuer. |
| NIP-98 authentication (structural) | present | NIP-98 | auth::nip98 |
Always on. |
| NIP-98 Schnorr signature verification | present | BIP-340 | auth::nip98::verify_schnorr_signature |
Feature nip98-schnorr. |
| Solid-OIDC 0.1 | present | Solid-OIDC §3–§5 | oidc |
Feature oidc. |
| DPoP | present | RFC 9449 | oidc::dpop |
Proof binding + iat/htm/htu. |
DPoP jti replay cache |
present | Solid-OIDC §5.2, RFC 9449 §11.1 | oidc::replay |
Feature dpop-replay-cache. |
| WebSocketChannel2023 notifications | present | Solid Notifications 0.2 | notifications::websocket |
|
| WebhookChannel2023 notifications | present | Solid Notifications 0.2 | notifications::webhook |
|
Legacy solid-0.1 WebSocket adapter |
present | Legacy SolidOS | notifications::legacy |
Feature legacy-notifications. |
| SSRF guard | present | OWASP ASVS §10.8 | security::ssrf |
Feature security-primitives. |
| Dotfile allowlist | present | Solid Protocol §3.5 | security::dotfile |
Default: .acl, .meta. |
| Layered config loader (JSS-compatible) | present | — | config |
Feature config-loader. |
| Well-known Solid discovery document | present | Solid Protocol §4.1.2 | interop::well_known_solid |
|
| WebFinger (JRD) | present | RFC 7033 | interop::webfinger_response |
|
| FS backend | present | — | storage::fs |
Default. |
| In-memory backend | present | — | storage::memory |
Default; used in tests. |
| S3 backend | present | — | storage::s3 |
Feature s3-backend. |
| ActivityPub federation | functional | — | crate solid-pod-rs-activitypub |
Sprint 10. Rows 102–108, 131. |
| Git HTTP backend | functional | — | crate solid-pod-rs-git |
Sprint 10. Rows 69, 100. |
| Embedded Solid-OIDC IDP | functional | — | crate solid-pod-rs-idp |
Sprint 10 + 11. Rows 74–81, 130 (Sprint 11 promoted Passkeys + Schnorr). |
| did:nostr resolver + embedded relay | functional | — | crate solid-pod-rs-nostr |
Sprint 10. Rows 89, 90, 101, 132. |
| did:key (Ed25519/P-256/secp256k1) + self-signed JWT verifier | functional | W3C did:key + LWS 1.0 SSI | crate solid-pod-rs-didkey |
Sprint 11 (NEW). Row 153. |
Full parity tracking against the reference JavaScript implementation
lives in
crates/solid-pod-rs/PARITY-CHECKLIST.md
→ ~100 % spec-normative parity / ~97 % strict on the full 121-row tracker
(Sprint 11 close; 835 workspace tests, 0 failing, clippy -D warnings clean).
Prose commentary in
crates/solid-pod-rs/GAP-ANALYSIS.md,
and an agent-oriented integration guide with per-module JSS source
breadcrumbs in
crates/solid-pod-rs/docs/reference/agent-integration-guide.md.
The solid-pod-rs library crate, the solid-pod-rs-server binary,
and five sibling crates
(solid-pod-rs-activitypub, solid-pod-rs-git, solid-pod-rs-idp,
solid-pod-rs-nostr, solid-pod-rs-didkey) constitute the v0.5.0-alpha.1
shipping surface. Every module listed in the feature matrix above is
live, tested, and gated behind a stable Cargo feature.
835 tests pass across the workspace; 0 failing; clippy -D warnings
clean against the full feature set
(oidc,dpop-replay-cache,legacy-notifications,jss-v04,acl-origin,security-primitives,config-loader,nip98-schnorr,webhook-signing,did-nostr,rate-limit,quota,passkey,schnorr-sso).
Library surface (crate solid-pod-rs):
storage—Storagetrait + FS / Memory / S3 backends.ldp— LDP Basic Containers, resource CRUD, conneg, PATCH (N3 / SPARQL-Update / JSON Patch),Prefer, conditional + range requests,.meta/.aclcontent negotiation.wac— deny-by-default evaluator withacl:defaultinheritance,acl:originenforcement, WAC 2.0 conditions framework (acl:ClientCondition,acl:IssuerCondition), JSON-LD + Turtle ACL parsers with size + depth caps.webid— profile documents emittingsolid:oidcIssuerand CID-bound storage links.auth::nip98— NIP-98 HTTP authentication; BIP-340 Schnorr signature verification undernip98-schnorr.oidc— Solid-OIDC 0.1 with DPoP proof signature verification (RFC 9449 §4.3athbinding, algorithm allowlist,jtireplay cache), SSRF-guarded JWKS fetcher with DNS-rebinding defence, RFC 7638 canonical thumbprints.notifications— WebSocketChannel2023, WebhookChannel2023 with RFC 9421 Ed25519 signing + circuit breaker, legacysolid-0.1adapter with WAC read-check on subscribe.security— SSRF guard (RFC 1918 / loopback / link-local / cloud metadata), dotfile allowlist (.acl,.meta,.well-known,.quota.json), CORS policy, sliding-window LRU rate limiter.quota— per-pod.quota.jsonsidecar with atomic writes (P0 hardening, Sprint 8).multitenant—PodResolvertrait; path-based + subdomain modes.config— JSS-compatible layered loader (JSS_*env vars).interop—/.well-known/solid, WebFinger JRD, NodeInfo 2.1, did:nostr resolver (Tier 1 + Tier 3,alsoKnownAscross-verified).provision— pod bootstrap: WebID + containers + type indexes + public-read ACL.
Binary surface (crate solid-pod-rs-server):
- Actix-web route table covering LDP verbs,
.well-known/*and did:nostr;PathTraversalGuard+DotfileGuardmiddleware; WAC enforcement on writes; optional rustls TLS.
All five sibling crates are functional and shipping. Integrators may take a dependency today.
| Crate | LOC | Rows | Landed |
|---|---|---|---|
crates/solid-pod-rs-activitypub |
2,394 | 102–108, 131 | Sprint 10 |
crates/solid-pod-rs-git |
1,299 | 69, 100 | Sprint 10 |
crates/solid-pod-rs-idp |
~4,400 | 74–81, 130 | Sprint 10 + 11 (Passkeys/Schnorr full) |
crates/solid-pod-rs-nostr |
2,177 | 89, 90, 101, 132 | Sprint 10 |
crates/solid-pod-rs-didkey |
858 | 153 | Sprint 11 (NEW) |
The did:nostr resolver shipped in Sprint 6 lives inside the core library
(interop::did_nostr) as well as the solid-pod-rs-nostr crate, so the
Tier 1 + Tier 3 DID flow is available either way.
solid-pod-rs is a Cargo workspace. Each crate has a single responsibility, with a strict one-way dependency gradient.
┌──────────────────────────────────────────────────────────────┐
│ crates/solid-pod-rs-server │
│ CLI + actix-web transport + signal handling. │
│ Wires `ConfigLoader` → storage → PodService. │
│ Has no protocol knowledge of its own. │
│ │ │
│ ▼ │
│ crates/solid-pod-rs │
│ Protocol primitives. Framework-agnostic. │
│ ├── storage — Storage trait + FS/Memory/S3 backends │
│ ├── ldp — Resources, containers, conneg, PATCH │
│ ├── wac — Access control (ACL + origin + inherit) │
│ ├── webid — WebID profile documents │
│ ├── auth — NIP-98 HTTP auth │
│ ├── oidc — Solid-OIDC 0.1 + DPoP + replay cache │
│ ├── notifications — WebSocket + Webhook + legacy adapter │
│ ├── security — SSRF guard + dotfile allowlist │
│ ├── config — JSS-compatible layered loader │
│ ├── interop — Well-known Solid + WebFinger │
│ └── provision — Pod bootstrap (WebID + containers + ACL) │
│ │
│ crates/solid-pod-rs-{activitypub, git, idp, nostr, didkey} │
│ Functional extension crates (Sprint 10 + 11). │
│ - activitypub: ActivityPub + HTTP Sig + NodeInfo + retry │
│ - git: Smart-HTTP git-http-backend bridge │
│ - idp: Solid-OIDC IdP + Passkeys + Schnorr │
│ - nostr: did:nostr resolver + BIP-340 NIP-01 relay │
│ - didkey: did:key (Ed25519/P-256/secp256k1) + JWT │
└──────────────────────────────────────────────────────────────┘
The library crate never constructs an HTTP server. Consumers own the
transport, the routing, and the runtime. The server crate is the
canonical example of wiring the library into actix-web; the patterns
it uses are documented in
crates/solid-pod-rs/examples/embed_in_actix.rs.
This split — formalised in
crates/solid-pod-rs/docs/explanation/architecture-decisions.md —
is load-bearing. It allows the library to be embedded in edge
workers and async runtimes that cannot host actix-web::HttpServer,
and it allows the binary to evolve independently without forcing
library revisions.
solid-pod-rs-server loads configuration in layers, lowest
precedence first:
- Compiled-in defaults.
- A JSON or TOML file passed via
--config <path>. - Environment variables under the
JSS_*namespace.
The environment variable names are deliberately identical to those of the reference JavaScript server so that existing JSS deployment scripts, Kubernetes manifests, and Docker Compose files continue to work unchanged. Selected variables:
| Variable | Type | Purpose |
|---|---|---|
JSS_HOST |
string | Bind address (default 127.0.0.1). |
JSS_PORT |
u16 | Listen port (default 3000). |
JSS_BASE_URL |
URL | Externally visible base URL. |
JSS_STORAGE_ROOT |
path | Filesystem root when using the FS backend. |
JSS_OIDC_ISSUER |
URL | Identity provider discovery URL. |
JSS_WORKERS |
usize | actix-web worker count (default: CPUs). |
JSS_LOG_LEVEL |
string | trace |
JSS_DISABLE_DOTFILES |
bool | If set, no dotfiles are served even on allowlist. |
The full set, including S3, webhook, and notification tuning keys,
is listed in
crates/solid-pod-rs/docs/reference/env-vars.md.
solid-pod-rs ships two authentication paths, used in isolation or
side-by-side. Both funnel into the same AuthContext, so WAC
evaluation is transparent to the upstream mechanism.
NIP-98 (primary). HTTP authentication over Nostr-signed events.
Always compiled; the structural verifier runs unconditionally.
Enabling the nip98-schnorr feature activates BIP-340 signature
verification over the canonical NIP-01 event hash. This is the
recommended path for sovereign-identity deployments: no IDP, no
client registration, no token exchange — clients sign a
timestamp-bound event per request and the server verifies it.
Solid-OIDC (optional). Standards-track Solid identity, gated
behind the oidc feature. Discovery, dynamic client registration,
ID-token verification, and DPoP proof-of-possession per RFC 9449 are
all implemented. Operators enabling dpop-replay-cache also get a
per-process LRU of seen jti claims, closing the DPoP replay window
defined in Solid-OIDC §5.2 and RFC 9449 §11.1. The cache is bounded,
clock-aware, and safe under concurrent access; benchmarks live in
crates/solid-pod-rs/benches/dpop_replay_bench.rs.
How-to guides:
WAC is evaluated deny-by-default. No ACL, no access. Every resource
is paired with a sibling .acl document (JSON-LD or Turtle); the
evaluator walks ancestors until it finds a declared ACL, honouring
acl:default for inheritance.
With the acl-origin feature enabled, acl:origin authorisations
are enforced against the request Origin header. Clients that omit
the header, or whose origin is not listed, are denied — matching the
WAC spec §4.3 semantics. With the feature off, origin is ignored for
backward compatibility with older clients.
Supported modes: acl:Read, acl:Write, acl:Append,
acl:Control. The evaluator returns a WacAllow structure ready to
be serialised into the WAC-Allow response header per the Solid
Protocol's transparency requirement.
See wac-modes.md
and debug-acl-denials.md.
Three notification surfaces are shipped:
- WebSocketChannel2023 — the current Solid Notifications 0.2
protocol. Subscribers
POSTaNotificationChannelresource and receive updates over a topic-bound WebSocket connection. - WebhookChannel2023 — identical event model, delivered as
outbound HTTP
POSTrequests with configurable retry and backoff. - Legacy
solid-0.1— a compatibility adapter for the SolidOS data browser's older WebSocket dialect. Gated behindlegacy-notifications. Enable it when you need SolidOS UI compatibility; leave it off for modern clients.
Events are generated from storage-layer mutations via a
publish/subscribe bus that is backend-agnostic; custom storage
backends emit events by calling NotificationBus::publish.
How-to guides:
The Storage trait abstracts the blob + metadata layer so the rest
of the crate is backend-agnostic.
fs-backend(default) — POSIX filesystem. Stores resources as regular files with sidecar.metadocuments for RDF metadata and.acldocuments for access control. Supports atomic rename semantics for concurrent writers.memory-backend(default) — in-processHashMap. Used for the test corpus and for ephemeral deployments (CI, demos, integration harnesses).s3-backend(opt-in) — AWS S3 and S3-compatible object stores (MinIO, R2, Backblaze B2). Metadata travels in object tags; ETags mirror S3's strong-consistency model.
Custom backends implement four methods (get, put, delete,
list) on top of a ResourceMeta + Bytes contract. The
custom_storage.rs
example walks through a Redis-backed implementation.
solid-pod-rs is a sibling implementation, not a rewrite. It implements the same Solid Protocol 0.11 surface as JavaScriptSolidServer and deliberately inherits JSS's AGPL-3.0 licence to preserve the ecosystem's network-service copyleft. The lineage is:
JavaScriptSolidServer (Node.js, AGPL-3.0)
│
├── reference implementation
│
community-forum-rs/pod-worker (Rust, AGPL-3.0)
│
├── Cloudflare Workers port; seeded LDP + WAC + NIP-98
│
solid-pod-rs (Rust, AGPL-3.0) ← you are here
JSS's ecosystem covenant is the reason we are AGPL rather than
permissive: a permissive relicence of AGPL-covered heritage would
weaken the protections the wider Solid ecosystem depends on. See
NOTICE for the full provenance chain
and attribution.
Compared with JSS, solid-pod-rs trades feature breadth for runtime properties: no Node.js dependency, single static binary, lower memory footprint, deterministic RDF serialisation, and compile-time feature gating. JSS remains the reference for features not yet ported — see the parity checklist for what we have, what we defer, and why.
Conformance targets:
- Solid Protocol 0.11
- WAC spec (2022-11-08)
- Solid Notifications 0.2
- Solid-OIDC 0.1
- NIP-98
- RFC 6902 — JSON Patch
- RFC 7232 — conditional requests
- RFC 7233 — range requests
- RFC 9449 — DPoP
Explicitly deferred: RDF/XML serialisation, live-reload script
injection, LDP Direct and Indirect Containers. Rationale recorded in
docs/explanation/architecture-decisions.md.
solid-pod-rs sits within a wider sovereign-data Rust ecosystem:
- URN-Solid — URN resolver for Solid resources.
- solid-schema — typed schema registry over Solid pods.
- Solid-Apps — reference client applications.
Full documentation lives in
crates/solid-pod-rs/docs/ and follows
the Diátaxis framework:
- Tutorials — learning-oriented walkthroughs for new users.
- How-to guides — goal-oriented recipes for specific tasks.
- Reference — exhaustive API and protocol documentation.
- Explanation — architectural background and design rationale.
The rendered documentation index is at
crates/solid-pod-rs/docs/README.md.
See CONTRIBUTING.md. In
brief: open an issue describing the change, run cargo test --all-features and cargo clippy --all-targets --all-features -- -D warnings before opening a pull request, and ensure any new
public surface has a doc comment and a corresponding test.
Security issues: please follow the disclosure policy in
SECURITY.md.
AGPL-3.0-only. Full text in LICENSE. If you operate
solid-pod-rs as a network-accessible service — which, by the nature
of a pod, you almost certainly will — §13 of the AGPL requires you
to provide corresponding source to your users. This matches the
reference implementation's licence and preserves the Solid
ecosystem's existing covenant.
solid-pod-rs has no Contributor Licence Agreement. Contributions are accepted under the project's licence via the standard GitHub pull request workflow; by opening a pull request you certify compliance with the Developer Certificate of Origin.