solid-pod-rs/CHANGELOG.md at main · DreamLab-AI/solid-pod-rs · GitHub
Skip to content

Latest commit

 

History

History
1110 lines (959 loc) · 60.5 KB

CHANGELOG.md

File metadata and controls

1110 lines (959 loc) · 60.5 KB

Changelog

All notable changes to solid-pod-rs will be documented in this file.

The format is based on Keep a Changelog.

[Unreleased]

[0.5.0-alpha.17] — 2026-10-20

The rest of the JSS plugins/forge feature set, the mirror hardening that lets the forge and the kind-30618 mirror ship on (ADR-2010), and the fixes from an independent security review. Parity: 264 rows, 99.2% strict (PARITY-CHECKLIST.md §26 and rows 245 to 264).

Added

  • solid-pod-rs-forge: JSON API (repo list, meta, tree, blob, commits, commit with structured diff), issue state/retitle, repo PATCH, labels, search, releases and archives, private repositories, FORGE_CONFIG, forks with hidden refs/forge/*, compare, pull requests, real merges (merge-tree --write-tree, compare-and-swap, git 2.38+), forge-held Blocktrails trails and the Anchors page, the NIP-34 builder, signer and relay publisher, POST /announce, GET /nostr, web-edit (POST api/repos/<o>/<n>/edit) and the ephemeral preview, an escape-first markdown renderer, and the vendored login widget with a page CSP.
  • solid-pod-rs-server: a long-lived forge service per data directory, read from FORGE_CONFIG and FORGE_* environment overrides; the mirror started at boot from FORGE_MIRROR_RELAYS and FORGE_MIRROR_FOLLOW; features forge-mirror and forge-full; X-Invite-Token redeemed at registration; --trust-proxy with cf-visitor; ActivityPub actor knobs (activitypub feature); cross-identity between a Nostr caller and its WebID; a configurable public origin (JSS_PUBLIC_BASE_URL, host allow-list).
  • solid-pod-rs: Turtle and N3 request bodies (oxttl), with N3 formulas and rules refused.

Changed

  • Mirror hardening: DNS pinned to the SSRF-approved address, remote clone URLs normalised, a repository size cap enforced by a watchdog that kills the git process group, cheap event filters before signature checks, a bounded per-repository queue, per-repository failure backoff, pushes to a mirrored repository refused, the trail bound to the followed key, the marked commit required to be reachable, replayed and far-future states dropped, refs/mirror hidden from upload-pack.
  • Security review fixes: cross-owner compare/PR heads refused in private mode (H1), NIP-98 and absolute URLs no longer trust forwarded host headers (M1), cross-fork fetches limited to the head owner or an open pull request (M2), did:nostr resolver pins the vetted address (M3), wall-clock limits on git grep/archive (L1), invite use counts and hashed tokens (L7), and more.
  • Behaviour to know about: behind a TLS-terminating proxy, NIP-98 URL matching needs --trust-proxy (or a configured public origin) to see https.

[0.5.0-alpha.16] — 2026-10-19

Tracks JSS 0.0.221 (8d68ceb) and the JSS plugins repository's forge at 63a16cb. Parity: 264 rows, 99.2% strict (see PARITY-CHECKLIST.md §24, §25).

Added

  • solid-pod-rs-forge: repo-held git-mark trails (gitmark module). A repository that commits a blocktrails.json (profile gitmark) now has it read at the default-branch tip and every mark's taproot address re-derived with gitmark's state bytes (SHA-256 of the commit hash as text). Served at <forge>/<owner>/<repo>/blocktrails.json with Access-Control-Allow-Origin: * for the hosted verifier. Address derivation needs the anchoring feature. Also: per-chain explorer links, anchor_tags (the r, anchor, chain, txo and commit NIP-34 tags) and did_short. Differential vectors were generated by running the JSS trailAddress and trailProgram.
  • solid-pod-rs-forge: did:nostr agents are displayed as did:nostr:<8 hex>… in owner crumbs, headings, the repo list and author names. Links, ids and paths keep the canonical hex.
  • solid-pod-rs-forge: kind-30618 mirror (mirror feature, default off). Follow (pubkey, name) pairs on configured relays and git fetch into refs/mirror/* when the agent's repository state changes; a branch moves only to a fetched commit whose committed trail lists the marked commit. Record served at api/repos/<owner>/<repo>/mirror ahead of the repo-exists check.
  • solid-pod-rs: mrc20::bt_trail_addresses, every link's taproot address in one linear walk.

Changed

  • CORS Access-Control-Expose-Headers now also lists Accept-Query, Last-Modified and WWW-Authenticate (JSS PR #629). Accept-Query is listed for parity; the server emits no such header yet.
  • Forge prefix precedence is pinned by a test (JSS #616): a git-looking URL under /forge reaches the forge, never the pod git lane's push_not_served. No behaviour change; the routes were already registered in that order.

[0.5.0-alpha.15] — 2026-10-19

Removed

  • solid-pod-rs-server no longer serves git push. git-receive-pack and its info/refs?service=git-receive-pack discovery are refused with 403 (push_not_served) for every caller, before any credential is read. The path never worked (the git service had no auth provider, so every push was refused), and serving it safely would have needed a WAC check on every pushed file. The route stays registered so the request cannot fall through to the LDP catch-all. The server's pre-receive guard module goes with it; the solid-pod-rs-git push guard is unchanged. Clone and fetch are unaffected.

[0.5.0-alpha.14] — 2026-10-18

Security

  • MCP skill reads are charged. get_skill and get_pod_skill returned the body of an acl:PaymentCondition-gated SKILL.md without debiting the caller, so a paid skill was free over MCP. Both now go through the same charging check as read_resource.

  • Dependency advisories. rustls 0.23.46+ and quinn-proto 0.11.19, and the unmaintained serde_yaml (unsafe-libyaml) replaced by the API-compatible serde_norway.

  • One hardened git constructor (solid_pod_rs_git::hardened_git, hardened_git_async, hardened_cgi_env) now backs every production git spawn in the git, forge and server crates: no repo-local hooks, no fsmonitor, no system/global config, no terminal prompt. The git-http-backend CGI gets the same settings through GIT_CONFIG_COUNT.

  • Solid-OIDC aud is checked. verify_access_token requires aud to be (or contain) solid; verify_access_token_with_policy takes an explicit AccessTokenPolicy. A kid resolving to a JWK of the wrong key family for the header alg is rejected. New WebIdProfileSource and verify_webid_issuer_binding check that the WebID profile declares the token's iss as solid:oidcIssuer; the caller supplies the profile fetch.

  • Dev bearer behind a feature. verify_dev_bearer exists only with the dev-bearer feature (off by default) and now requires aud = solid-pod-rs-dev-bearer and a lifetime of at most 24 h.

  • Case-insensitive filesystems. The dotfile guard refuses doc.ACL, X.META.JSON and similar case variants of guarded names. New public solid_pod_rs::security::dotfile::fold_case folds the way a case-insensitive filesystem does, including the long s, the sharp s and the Latin ligatures that plain lower-casing leaves alone; both the dotfile guard and the well-known trail guard use it.

Changed

  • Every production git site, including the git-http-backend CGI, now ignores system and global config. This drops operator safe.directory entries, and solid-pod-rs-server install loses credential helpers, proxy settings and terminal prompts (it authenticates with its explicit http.extraHeader).
  • verify_access_token rejects tokens whose aud does not name solid (previously any aud verified); CachedFetcher::verify_access_token_cached_with_policy is the cached equivalent. Deployments with another audience use verify_access_token_with_policy.
  • Dev-bearer tokens need aud and exp - iat <= 24 h; build with --features dev-bearer to use them.

[0.5.0-alpha.13] — 2026-10-14

Security

All fixes are in solid-pod-rs-server and solid-pod-rs-git. Every solid-pod-rs-server release from 0.4.0-alpha.3 through 0.5.0-alpha.12 is affected and is yanked; upgrade to 0.5.0-alpha.13.

  • COPY authorises its source. COPY requires read access on Source (the dotfile allowlist applies), rejects relative, query, fragment and cross-host values with 400, and accepts same-host absolute URLs. The source .acl sidecar is copied only for a caller with Control on both ends who keeps Control. A pay-per-read source debits the caller once.
  • Glob GET authorises each child. Unreadable children, .acl, .meta and .prov.ttl are no longer merged; pay-per-read children are left out; a response with private parts carries Cache-Control: no-store and Vary: Authorization.
  • One path canonicaliser now feeds both the permission check and the storage call on every route (REST, COPY, glob, MCP). Previously MCP and COPY could reach guarded files through //, /./ or a missing leading /.
  • Server-managed state needs Control. /.well-known/token/, /.well-known/gitmark/, /.well-known/prov/ and /.well-known/webledgers/ (except blocktrails.json) require acl:Control to read or write, over HTTP and MCP. Trail files hold anchor signing keys; the web ledger holds every balance. Operators who served a public-read root, or enabled --mcp, should rotate trail keys and audit ledger balances.
  • MCP enforces the dotfile allowlist and routes through the REST read, write and quota path. Previously a Writer could create .git/config, *.meta.json and temp-file names via MCP.
  • Container index.html negotiation checks Read on the index.html child, not only the container.
  • Git push guard. GitHttpService::with_push_guard rejects, before anything is checked out, any push touching server-managed state, sidecars or disallowed dotfiles, and always refuses symlink, submodule and signed entries. Pushed symlinks are checked out as plain files (core.symlinks=false). The server's own git calls drop repo-local hooks, fsmonitor and global config.
  • Anchoring no longer swallows storage errors. Only NotFound means absent; an unreadable trail or epoch file fails the anchor with a recorded anchor_error. Failed refunds are logged and queued in /.well-known/prov/refunds-pending.json.
  • Anchoring is serialised and durable. Path-keyed locks cover trail and epoch advances. A durable intent file stores the expected txid and signed rawHex before each broadcast, is re-sent if the node has not indexed it, and is checked against its raw transaction. Mempool requests time out (5 s connect, 20 s total; JSS_PAY_MEMPOOL_CONNECT_TIMEOUT_SECS, JSS_PAY_MEMPOOL_TIMEOUT_SECS). epoch.json growth is capped.
  • Payment ledger race. Anchor charges and refunds hold the payment lock for their read-modify-write; concurrent debits could previously lose updates.
  • Token-transfer txid now excludes the witness (decode_tx_info). Payment intents written by earlier builds carry wrong txids: inspect state.json before deploying.

Changed

  • GitmarkAnchorer<M> requires M: MempoolTxPresence (implemented for MempoolHttpClient).
  • POST /_prov/anchor returns 202 {status:"pending"} without refunding when a broadcast outcome is ambiguous; failures before broadcast still refund (502).
  • /pay/.withdraw and /pay/.buy return 503 with Retry-After when a trail is busy for more than 2 s.
  • Public or Write-only GET of /.well-known/webledgers/* now returns 401/403.
  • GIT_CONFIG_GLOBAL=/dev/null for marks drops any operator safe.directory.
  • Docs: removed the non-existent standalone example; use cargo run -p solid-pod-rs-server.

Known gaps

Git push is not enabled over the server route (no auth provider); when it is, authorisation is Write-on-root only. Trail and epoch locks are in-process and held across network calls. Several Command::new("git") sites outside marks are not yet hardened. Case-insensitive filesystems are not supported. Solid-OIDC helpers do not check aud or issuer-to-WebID; the dev bearer token is a non-standard format.

[0.5.0-alpha.12] — 2026-10-03

Added

  • Per-link trail verification, ported from blocktrails/verify 043e7af (solid_pod_rs::blocktrail). walk_blocktrail recomputes every mark's output key from a trail's pubkeyBase and its states (a string state hashed as its text, an object as its JCS; a git-mark trail with no states reads them from its TXO URIs); verify_blocktrail then checks each mark's transaction exists, carries the recomputed key, spends the previous mark, is confirmed and matches its recorded amount, and gives the page's verdict: verified, confirmed (marks on-chain, commitments not checkable) or partial. verify_anchor_chain runs the same checks backwards from a single head outpoint, for trails that keep only their head. Every label and verdict is pinned against the page itself run under a stub DOM (tests/fixtures/blocktrails/verify-trail-vectors.json, generated by gen-verify-trail-vectors.mjs from blocktrails/verify 043e7af, blocktrails/git-mark b852d7d and sidestr/spec e8deb63 keys.mjs), including git-mark's live trail (three marks, captured from mempool.space testnet4) verified, and the same trail with two states swapped refused from the swap on. One deliberate difference: a mark that does not spend its predecessor is broken link here; the page shows "chain link ✗" but still labels it verified.
  • Blocktrail, the shared blocktrails.json type, in the shape of blocktrails/spec's gitmark profile §5.2 (@type, version, profile, pubkeyBase, chain, states, txo as TXO URIs txo:<chain>:<txid>:<vout>?amount=&commit=), and BlocktrailTxo parsing and formatting TXO URIs as git-mark's parseTxoUri / formatTxoUri do. provenance::BlocktrailEnvelope is now an alias of it. Files in the earlier shape (@id, genesis, txo entries {outpoint, blockheight}) still parse and serialise back byte for byte as read.
  • Git-mark trails for new anchors. bitcoin_tx::gitmark_genesis / gitmark_advance build marks whose state is the commit as 40-hex text with no MRC20 wrapper (40 or 64 lowercase hex accepted); gitmark_advance refuses to extend a trail whose newest mark is not the key its states derive. Server: mempool::GitmarkAnchorer (genesis, anchor, verify), trails kept at /.well-known/gitmark/{name}.json with the public blocktrails.json beside them; build_anchorer uses it whenever no MRC20 trail is already issued under the name (default name prov).
  • bt_address(.., "gitmark") encodes with the gm HRP (git-mark NETWORK_HRP); bc and tb are unchanged.
  • mrc20::TxIn and TxInfo::vin (the outpoints a transaction spends; the server's mempool client now reads them), BuiltTx::txid, and bitcoin_tx::decode_tx_info.
  • scalar_mod_n (already reducing int(h) mod n since 0.5.0-alpha.11) pinned against keys.mjs taggedScalar's own reduction for hashes at and above n.
  • tests/doc_vocabulary.rs: no crate's rustdoc or README says "single-use seal"; the provenance docs now speak of marks, trails and anchors.

Changed

  • verify_mrc20_anchor and MempoolBlockAnchorer::verify walk every link instead of checking only that a UTXO sits at the head address: from the head the walk follows each spend back to genesis, and every mark must be the key its prefix of states derives. Signatures are unchanged; a head still in the mempool is accepted as before. Mrc20AnchorResult gains report; MempoolBlockAnchorer::verify_report exposes the per-mark report. An anchor now stays verifiable after a later anchor spends it. Issued MRC20 trails, their addresses and anchor_state's urn:mono:op:anchor states are untouched.
  • Breaking for struct literals (pre-1.0): TxInfo gains vin, BuiltTx gains txid, Mrc20AnchorResult gains report; BlocktrailTxo is now the TXO URI type and BlocktrailEnvelope::new_gitmark_profile is replaced by Blocktrail::gitmark.

[0.5.0-alpha.11] - 2026-10-02

Added

  • Blocktrails "the rule as it is" (blocktrails/spec ef54a08), audited and pinned. The mrc20 chained-key derivation already follows it: the tweak is BIP 341's TapTweak hash of x(P) || sha256(state), added to the full point (never its even-y lift; not BIP 341's derivation), the secret chains as d + Σt, and each output key is x(P) itself. A state string is hashed as its UTF-8 bytes, which is blocktrails/verify 043e7af's stateHash (a string state as its text, an object as its JCS). No arithmetic changed, so no issued MRC20 trail address changes: the pinned CHAINED_GOLDEN / CHAINED_GOLDEN_JCS rows, stored trails (pubkey_base 66-hex, state_strings as stored) and the anchor flow are untouched. Added:
    • tests/fixtures/blocktrails/keys-walk-vectors.json, generated by the upstream JS (gen-keys-walk-vectors.mjs beside it: sidestr/spec e8deb63 keys.mjs and address.mjs on bitcoin-desktop/schema b8cbf63), and tests/blocktrails_keys_walk.rs reproducing every point, x-only output, chained secret and tb/bc address: the CHAINED_GOLDEN inputs, object states as JCS, an 03 base with an odd-y intermediate, bare-x bases, mixed text and object states, and git-mark's live trail. With SIDESTR_SIDING and SCHEMA set the generator also runs live and must reproduce the file.
    • blocktrails/git-mark b852d7d's live-trail pin (base 0273c7f6…, three commits as text, outputs e403de73…, 3ef39ed4…, d7abfea9…) and its swapped-commit refusal, as unit tests.
    • bt_base_point: a base key read as keys.mjs basePoint reads it (a compressed point keeps its parity; a bare 64-hex x or did:nostr:<x> is the even-y 02 point; a did:nostr Multikey fe701…). The derivation functions and bt_address now accept those forms; 66-hex callers are unchanged. verify_mrc20_anchor still requires the 66-hex compressed point.
    • bt_normalize_privkey (keys.mjs normalize: the holder of a bare-x base normalises the secret once), bt_trail_outputs and bt_verify_trail_outputs (blocktrails/verify's per-mark walk: every link checked, mismatch at index i), and bt_state_string (text for a string state, JCS otherwise).
    • The git-mark pin proves the shared arithmetic, not the profile: git-mark trails are the gitmark profile on tbtc4, the trails kept here are MRC20 on testnet4.

Changed

  • The chaining scalar is int(TapTweak hash) mod n, zero refused, as the spec and keys.mjs taggedScalar state. A hash at or above the group order used to fail the derivation and is now reduced mod n (by libsecp256k1); a zero tweak used to be added as a no-op and now refuses the state. Both happen with probability about 2^-128 or less, so no derived address can differ. An uncompressed (04…, 130-hex) base, which was returned as-is with no states and failed with any state, is now refused uniformly.

Removed

  • The TXO stand-in deposit (ADR-2008 D6). POST /pay/.deposit no longer has a branch that credited (vout + 1) * 1000 sats for any parseable txid:vout. That free-money oracle is deleted, together with its gate (AppState::deposit_txo_standin_enabled, --deposit-txo-standin, DEPOSIT_TXO_STANDIN_ENABLED). Any non-MRC20 deposit body now gets 501 and credits nothing. MRC20 deposits are unchanged. payments::parse_txo_uri stays as a pure parser (nostr-bbs-pod-worker uses it). This is breaking for solid-pod-rs-server: the public AppState field and the CLI flag are gone.

Changed

  • Bitcoin primitives come from rust-bitcoin (ADR-2008 D1-D2). bitcoin_tx and the mrc20 anchor module no longer hand-roll any Bitcoin or secp256k1 primitive. Transaction and CompactSize serialisation, txid byte order, P2TR scripts, the BIP-341 TapTweak and TapSighash, the key-path secret tweak and BIP-340 signing and verification come from bitcoin 0.32.102 and its re-exported secp256k1 0.29 (libsecp256k1). Bech32m addresses come from bitcoin::Address. The hand-written mod-n arithmetic (add_mod_n, neg_mod_n, sub), both tagged-hash helpers and the bech32m encoder are deleted. The mrc20 feature now enables dep:bitcoin instead of dep:k256 (k256 stays for NIP-98, LWS-CID and did:nostr). Output is byte-identical: the three JSS golden cases, the published BIP-340/341/350 vectors and pinned chained-key derivations all pass unchanged.

  • Behaviour differences, all on malformed input: build_transaction, mint_token and bt_derive_chained_privkey require an exactly 32-byte secret key (a 24-31-byte key used to be left-padded, and in build_transaction then panicked); p2tr_script and bt_address refuse an x-only key that is not a curve point (a P2TR output to it is unspendable); verify_keypath_signature refuses a sighash that is not 32 bytes; amounts above 21 million BTC are refused; the old 65,535-byte cap on scripts and counts is gone.

  • Breaking (solid-pod-rs): WebLedger balances change only through receipts (solidpayorg/teller 7c00cea lib/teller.mjs:40-58). WebLedger::credit and WebLedger::debit, and credit_currency / debit_currency, are now pub(crate), and the id and entries fields are crate-private (read them with id() and entries()). The public mutators are credit_by_outpoint(account, currency, txid, vout, value) (the outpoint is the receipt; a second credit of it returns ReceiptOutcome::AlreadyApplied) and debit_by_payout(account, amount, txid) (the payout txid is the receipt). The pod's existing flows keep three receipt-bound helpers: reverse_payout(txid) restores a recorded payout whose broadcast definitely failed, and charge / refund(Charge) take and return an access fee (per-request PaymentCondition and the _prov anchor price). None of these can raise a balance beyond what a recorded deposit put there. Accounts are read as teller reads them (did:nostr:<64 hex>, a bare x, or a Multikey's x).

  • The ledger document carries teller's identity. New LedgerGenesis (operator, name, currency, created, confirmations) and WebLedger::with_genesis: hash = sha256(JCS(genesis)), id = urn:webledgers:<hash>, checked by check_genesis (teller checkLedger). The document gains hash, genesis, deposits, applied and payouts; description and created become optional so a teller-written document loads. Documents without a genesis still load unchanged. The server stamps a genesis (operator = the issuer's x) only on a ledger that holds nothing yet, and refuses to read a ledger whose hash does not match its genesis. Verified against teller's own output (tests/fixtures/teller/ledger-7c00cea.json).

  • Breaking (solid-pod-rs): TokenConfig gains accepted_issuers (#[serde(default)], so stored configs still load; struct literals need the field) and accepts_issuer.

  • /pay/.buy, /pay/.withdraw and /pay/.withdraw-sats debit through debit_by_payout keyed by the signed transaction's txid; broadcast recovery reverses that payout. An intent left by the old code, with no payout receipt, is kept for operator review instead of being compensated.

  • The /pay/.balance and /pay/.address docs no longer claim an auto-detect scan: the balance is read from the ledger only.

  • Breaking (solid-pod-rs-server): the paying routes take a did:nostr account only. /pay/.deposit, /pay/.buy, /pay/.withdraw and /pay/.withdraw-sats now answer 400 (an account is a did:nostr identifier) for a principal teller's accountOf rejects, which includes the https WebID a dev bearer token yields. NIP-98 callers are unaffected.

  • CI runs doctests: cargo test --doc per core feature set and cargo test --workspace --doc. --all-targets skips doctests, so the compile_fail gate keeping WebLedger::credit/debit crate-private and the teller genesis-hash doctest were not running; tests/ci_doctest_gate.rs fails if the workflow drops them again.

Security

  • MRC20 deposits are bound to this pod's token. POST /pay/.deposit now refuses a trail whose anchor.pubkey is not the configured pay_config.token.issuer (or one of the new accepted_issuers) with 403, and a state whose ticker is not pay_config.token.ticker with 400, both before any mempool call. Previously anyone could mint their own trail under the pod's ticker, transfer it to the pod's address and be credited.
  • A deposit credits its ticker, never sats. The verified amount lands in the caller's ledger[ticker] balance; the satoshi balance is untouched. Balances already credited as sats by earlier deposits are left as they are.
  • One coin pays once. Besides the mrc20:<sha256(JCS(state))> replay key, a deposit is now refused (400, Replay: … outpoint …) when any UTXO at its derived anchor address is already a deposit receipt; the lowest outpoint is recorded as the receipt.

[0.5.0-alpha.10] - 2026-10-01

Tracks the did:nostr parity model as reconciled upstream in nostrcg/did-nostr#145 (closing #144): the identifier is the x-only key; a resolver holding only the identifier emits 0x02; a document the controller publishes MAY carry 0x03 when it holds the full key and y is odd; verifiers accept both. All eight workspace crates are re-pinned to 0.5.0-alpha.10.

Added

  • Full-key Multikey encoding (did_nostr_types, re-exported by solid-pod-rs-nostr::did): format_multibase_public_key(&k256::PublicKey) and format_multibase_sec1(&[u8]) emit fe70102…/fe70103… by the key's actual parity; render_did_document_published(&k256::PublicKey) renders the controller-published document. parse_multibase_sec1 decodes the full point a document carries, parity kept; NostrPubkey::from_public_key / to_even_public_key convert between the identifier and points (an identifier denotes the 0x02 point). Reading and encoding agree with basePoint() / multikey() in sidestr/spec PR #28, checked against its keys-vectors.json encoding fields. No key-arithmetic (tweak) API is added; that waits for PR #28 to merge.
  • Upstream conformance vectors. tests/fixtures/did-nostr/ vendors test-vectors-generated.json from nostrcg/did-nostr@4ea80d8; tests/did_nostr_vectors.rs runs decode_even_parity, decode_odd_parity, the key-transformation, minimal-document and error vectors, plus encode tests for odd-y full keys.

Changed

  • solid-pod-rs-git::write_agent_identity publishes the controller's parity. When the secret key is supplied, agent.did.json is rendered from the full key, so an odd-y key yields fe70103…. A secret that is malformed or whose public key does not match pubkey_hex is now refused (nothing is written) rather than silently git-configured beside a document for another key. Without a secret the document is unchanged (fe70102…).
  • Feature did-nostr-types now pulls k256 (pure Rust, wasm32-safe) for point validation and full-key encoding.
  • Docs no longer call 0x02 invariant. MULTIKEY_PREFIX, format_multibase_schnorr, render_did_document, the interop/WebID notes and the git identity invariants now describe 0x02 as the identifier-only default and 0x03 as permitted for controller-published documents.

Security

  • rustls 0.23.45. RUSTSEC-2026-0285 (TLS 1.3 handshake messages accepted across encryption-level boundaries) turned the CI cargo-audit and cargo-deny jobs red on a docs-only commit, because the advisory database moves independently of the tree. The lockfile now resolves the patched in-semver release; both advisory gates pass again.

Changed

  • Advisory exceptions re-verified and re-dated (2026-09-21). RUSTSEC-2026-0258 is patched only at h2 >= 0.4.16, a different major line from the h2 0.3 that actix-http 3.13.3 pulls unconditionally, so there is no in-semver fix to take; actix-web is taken with default-features = false, so no HTTP/2 listener is enabled. Rationale corrected and review moved to 2026-12-20 in both deny.toml and .cargo/audit.toml.
  • README told the truth about the audit gates. The status section claimed cargo audit --deny warnings fails on RUSTSEC-2026-0258; that advisory has been a dated, justified exception in both gates since 2026-08-20 and the gates pass. The still-open code audit findings are unchanged and now stated separately from the supply-chain gates.

[0.5.0-alpha.9] - 2026-09-06

A closeout and CI-hygiene release. The public surface gains the OIDC compatibility matrix, WAC policy outcomes and provenance receipts; the registry set is brought back into step (every sibling crate had stayed at 0.5.0-alpha.7 on crates.io while the root crate moved to alpha.8). All eight workspace crates are re-pinned to 0.5.0-alpha.9; the superseded registry versions are yanked.

Added

  • OIDC compatibility matrix, WAC policy outcomes, provenance receipts. Cache-control and mempool tests; the ADR pack and estate closeout records (40f160c).
  • RateLimiter::check_at(key, now). The LRU limiter's clock-parameterised check is public, with a runnable example; check is exactly check_at(key, Instant::now()). Tests drive virtual time and no longer sleep, which also fixed a wall-clock flake under tarpaulin (62376e9).

Fixed

  • chacha20 0.10.2. Both 0.10.0 and 0.10.1 were yanked upstream; the lockfile now resolves the clean patch, so cargo deny and cargo audit pass again (62376e9).
  • rustdoc intra-doc links. Four links that only fail under -D rustdoc::broken-intra-doc-links on stable: a bare variant name in wac/resolver.rs, and three short names in the auth::replay module docs, which rustdoc resolves in the parent module's scope because auth/mod.rs also documents the pub mod declaration (62376e9).

Documentation

  • ADR corpus consolidated into a living ground truth with a thin ledger (e093e88, d6ac7f5); ADR-061 records the LAN Bitcoin node anchoring and dual-network posture (f5a4d72, 0ccad60).
  • Release CI gates use real action SHAs and the root deny.toml (db96c3c); the Codecov upload is non-fatal, tarpaulin --fail-under is the coverage gate (75287ea).

[0.5.0-alpha.8] - 2026-08-20

A feature-parity and hardening bump. The public surface loses the unfinished S3 backend and gains capability-sandboxed filesystem storage, an atomic quota reservation path, and an observer-atomic payment ledger; the ActivityPub, git, IdP and nostr siblings each pick up a targeted correctness or hardening fix. All eight workspace crates are re-pinned to 0.5.0-alpha.8.

Removed

  • S3 storage backend dropped. The s3-backend feature, the aws-sdk-s3 dependency, the StorageBackendConfig::S3 config variant, and the JSS_S3_BUCKET / JSS_S3_REGION / JSS_S3_PREFIX env vars are gone. Storage is now filesystem or memory only; downstream consumers needing an object store supply their own Storage implementation. An unsupported JSS_STORAGE_TYPE (including s3) is now preserved through the env layer so tagged-enum deserialisation fails loudly, rather than silently falling back to filesystem storage. The core / wasm dependency surface shrinks.

Security

  • Filesystem backend is capability-sandboxed (cap-std). FsBackend now holds a cap_std::fs::Dir handle rooted at the pod store, so path traversal is prevented at the OS-handle level rather than by string inspection alone. fs-backend pulls cap-std.
  • Atomic quota reservation. QuotaPolicy gains a reserve method that performs check-and-commit under a per-pod async lock, so concurrent writers to the same pod can no longer each pass a pre-check and collectively overshoot the cap. The existing check + record pair is retained as the non-atomic cooperative discipline; record and reconcile also take the per-pod lock now. New security.default_quota_bytes config field.
  • Observer-atomic payment state. The payment handlers now commit the Web Ledger, replay set, order book and exchange together as a single state.json document via one Storage::put; the legacy per-concern documents (webledgers/ledger.json, replay.json, offers.json, pool.json) are demoted to best-effort compatibility mirrors, consulted on read only as a first-run bootstrap fallback. This closes a crash window between the ledger and replay writes that could double-credit a deposit. Adds base64 / hex / hmac / sha2 to the server for TXO txid derivation and dev-bearer HMAC auth.
  • HTTP-signature covered-component validation (ActivityPub). The verifier now rejects a well-formed Signature header that omits or duplicates a required covered component (SigError::MalformedSignature).
  • IdP: spoofable header path removed. An AuthenticatedUserId extractor replaces the trust-the-X-Authenticated-User-header path, and the DPoP replay cache is wired into the provider.
  • Git CGI execution timeout. A 120 s guard kills a hung git-http-backend child, surfaced as GitError::BackendFailed with exit_code: None.
  • Nostr relay limits. RelayLimits / dispatch_message_with_limits bound per-connection relay message handling in the WebSocket path.

Changed

  • solid-pod-rs-forge enables nip98-schnorr + nip98-replay on its core dependency and makes agent resolution async with replay handling.
  • Server transport trimmed. actix-web is taken at default-features = false, features = ["macros"]; rustls-pemfile is dropped from the tls feature. lru moves 0.16 → 0.18.
  • CI / release hardening. All workflow actions are pinned to commit SHAs; the core feature matrix is restructured (nested features object) and the now-removed s3-backend combination is gone; coverage (tarpaulin) is promoted to a required gate (--fail-under 60, fail_ci_if_error: true); cargo-deny runs against the workspace-root manifest. New security regression tests land for MCP/WAC, payment atomicity, quota enforcement and registration.

[0.5.0-alpha.7] - 2026-07-26

did:nostr CG spec 0.1.1 alignment plus the JSS 0.0.204 → 0.0.219 comparator sync. The upstream delta was audited commit-by-commit: most of it is either already covered here (raw-bytes NIP-98 hashing, POST-sidecar Control elevation, MCP write_acl accessTo, HEAD conneg — all structural or closed by the alpha.4 closeout) or not applicable to a compiled server (the entire 11-commit runtime-plugin cluster, node-oidc-provider/Fastify/n3.js specific fixes). One genuine gap and one latent bug were found and fixed.

Changed

  • did:nostr @context is now the CG spec 0.1.1 three-context form — ["https://www.w3.org/ns/did/v1", "https://www.w3.org/ns/cid/v1", "https://w3id.org/nostr/context"], with the DID Core context first as DID Core requires (and as the DID 1.1 transition expects). Previously the documents carried only the two-entry [cid/v1, nostr/context] form. Applied at the single canonical renderer (did_nostr_types::render_did_document), the interop malformed-hex fallback, and across tests, conformance fixtures, and fixture schemas in the nostr and didkey crates. Both forms expand to the same terms under JSON-LD, so consumers that parse rather than byte-compare are unaffected.

Fixed

  • Git smart-HTTP CORS on denied responses (JSS #548) — the WAC gate's 401/402/403 and the git service's error responses (e.g. 404 NotARepository) now carry the git CORS trio, so browser-based git clients see the real status and WWW-Authenticate challenge instead of a generic CORS error. The trio is now a single source of truth (service::GIT_CORS_HEADERS) shared by the preflight, success, error, and WAC-denial paths, and Access-Control-Allow-Headers gains Git-Protocol (protocol-v2 clients send it). The server-wide CORS middleware now defers to handler-set Access-Control-Allow-Origin (git/forge surfaces are deliberately *-open), making git CORS identical in wildcard and allowlist deployments; the global preflight header list also gains Git-Protocol.
  • IdP issuer slash-normalisation (JSS #524 class) — ProviderConfig::new now canonicalises the issuer to its trailing-slash form, so the discovery document's issuer, the RFC 9207 iss authorization-response parameter, and the access-token iss claim are byte-identical regardless of how the operator wrote the config. Previously discovery slash-normalised while token minting used the raw configured string — strict clients comparing the two byte-for-byte would reject the response.
  • IdP DPoP test helper minted HS256 proofs — the alg-confusion gate (audit sprint) rightly rejects HS*/kty=oct DPoP proofs, which had left two provider unit tests failing on main. The helper now mints real ES256 proofs with an embedded P-256 JWK.

Added

  • JSS_BODY_LIMIT env alias for JSS_MAX_REQUEST_BODY (upstream #474/#543 config name), for drop-in config parity. The canonical name wins when both are set; the default stays 50 MiB (upstream raised theirs to 20 MB, still below ours).
  • Blank-node round-trip regression test (JSS #536 class) — pins that blank-node subjects survive N-Triples re-parsing and stay _:-prefixed in JSON-LD @id (the n3.js bare-label bug is structurally absent here; the test keeps it that way).

Not ported (audited, classified N/A)

  • The JSS runtime plugin system (createServer({plugins}), --plugin, appPaths, api.mountApp/serverInfo/reservePath/plugins, 0.0.213–0.0.219): architectural mismatch — this workspace extends via compile-time Cargo features. Revisit via ADR only if a downstream needs recompile-free app mounts; if ever built, copy upstream's two safety invariants (parameterised reserved paths are read-only by default; validate path shapes after normalisation).
  • JWK Y-parity reconcile (#571), well-known profile-probe (#451), lastSubmission error transport (#514), hijack-then-throw sweeps (#412/#526), passkey WebView degradation (#556), tunnel credential passthrough (#530), port fallback (#477), Termux tmpdir (#518): fix JS-runtime constructs (JWK VM path, filesystem DID indexer, node oidc-provider, Fastify, inline browser JS, ops tunnel) that do not exist in this codebase.

[0.5.0-alpha.5] - 2026-07-15

Ships the new solid-pod-rs-forge crate and clears the supply-chain audit gate that was holding the release. All eight workspace crates are re-pinned to 0.5.0-alpha.5; the core / wasm surface is untouched (the forge is a default-off sibling, absent from the core dependency tree), so this is a drop-in upgrade for existing consumers.

Added

  • solid-pod-rs-forge — a Solid-hosted git forge (Phases 0–3), a clean-room Rust reimplementation of the JSS forge plugin built entirely on solid-pod-rs's own primitives (git smart-HTTP, provenance, NIP-98, did:nostr, WAC). Wired into solid-pod-rs-server behind the default-off forge feature; the core and wasm surfaces gain zero new dependencies. Complete and test-green (95 tests): the XSS-by-content-type spine, Tier-1 git hosting + browse porcelain (tree/blob/raw/commits/branches/tags), Tier-2 issues over an atomic FsSpineStore with a two-phase pod-write coordinator, and the Tier-2.5 f1.* HMAC push-token path for podless did:nostr identities. Phases 4–7 (forks/PRs, polish, Bitcoin anchors, NIP-34) are feature-scaffolded and compiling, not yet implemented. The forge enforces its own namespace guard and own-area SSRF check rather than the pod WAC ACL evaluator — see the /forge route notes added to the solid-pod-rs-server crate //! docs.

Fixed

  • Supply-chain: two yanked spin versions cleared (CI/audit gate) — the transitive spin crate appeared twice in the lockfile (0.9.8 via flume/lazy_static, 0.10.0 via crc-fast), and both were yanked from crates.io, which failed cargo-deny (error[yanked]) and cargo-audit (2 denied warnings) and blocked the release. Resolved per this repo's own audit policy — in-semver fixes are applied by cargo update, not ignored — by pinning the lockfile to the non-yanked in-semver successors spin 0.9.9 and spin 0.10.1. No source or API change; lockfile only.

[0.5.0-alpha.4] - 2026-07-03

The closeout security release. Four hardening fixes land on top of the 0.5.0-alpha.3 interop-convergence surface; the public API is unchanged, so this is a drop-in upgrade. All seven workspace crates are re-pinned to 0.5.0-alpha.4 and published together.

Security

  • NIP-98 single-use replay guard (native tier) — the stateless NIP-98 verifier accepts any token within its ±120s timestamp tolerance, so a captured Authorization header could be replayed inside that window. The new auth::replay::Nip98ReplayCache (feature nip98-replay, a bounded process-local LRU keyed on the canonical NIP-01 event id) closes the window: Nip98Verified now carries a signature-bound event_id, and solid-pod-rs-server records every accepted id in a shared LazyLock<Nip98ReplayCache> — a re-presented token is treated as unauthenticated and the WAC gate returns 401. TTL and size are tunable via SOLID_POD_NIP98_REPLAY_TTL_SECS / SOLID_POD_NIP98_REPLAY_MAX_SIZE. The cache is per-process; multi-replica deployments share no state.

  • Schnorr signature verification is now unconditional (fail-closed) — previously BIP-340 signature verification ran only under the nip98-schnorr feature; a build that dropped the feature silently degraded to structural checks alone and would accept any forged pubkey. verify_at_with_policy now calls verify_schnorr_signature on every path: with nip98-schnorr it verifies for real, and without it the call is a fail-closed stub returning PodError::Unsupported, so a mis-configured build denies authentication rather than granting it. A new const fn assert_schnorr_verification_enabled (present only under nip98-schnorr) is referenced in const context by solid-pod-rs-server, turning any regression that removes signature verification — a resolver change, a dropped dependency, a feature-unification slip — into a compile error rather than a fail-open server.

  • WAC acl:origin enforcement wired into the request path (F4) — the acl-origin evaluator support shipped previously but the server never fed it a request Origin, so origin-restricted ACLs were inert. The server now threads the HTTP Origin header through enforce_read_ctx / enforce_write_ctx into the evaluator's RequestContext, so an ACL bearing acl:origin triples gates cross-origin reads and writes (CSRF defence). Behaviour is conservative: plain ACLs with no acl:origin triple are unaffected, origin-less requests (server-to-server, git smart-HTTP, curl) are denied only where an ACL explicitly restricts origins, and acl:Control bypasses the origin gate by design so an owner can always repair a mis-configured ACL from any origin. Note: acl:origin is the only WAC 2.0 condition satisfiable end-to-end today — the client_id and issuer conditions remain unsatisfiable because the server does not yet surface an authenticated OIDC client_id / issuer into the RequestContext; those conditions therefore still evaluate deny.

Added

  • GET /api/exports/all — JSON-LD time-chain pod export route (parity row 198) — solid-pod-rs-server now exposes the previously library-only export::export_pod_jsonld walker over HTTP under the export-jsonld feature (default-off, native-only). The route is owner-gated: the caller must hold acl:Control on the pod root, because the bundle can expose every resource (including /private/* when ?include_private=true), bypassing per-resource ACLs. Emitted with the export::EXPORT_CONTENT_TYPE media type.

Changed

  • Documentation across the changed crates now reflects the hardened posture: the NIP-98 verifier module doc states verification is unconditional and fail-closed; the provision.rs / solid-pod-rs-idp key-provisioning docs drop the stale "scaffold / todo!()" wording (the bodies are implemented and tested); the core lib.rs export section notes the route is native-only.

[0.5.0-alpha.3] - 2026-06-27

The interop-convergence release. Also the release that finishes shipping the workspace: 0.5.0-alpha.2 was published for the solid-pod-rs core crate only — the six sibling crates (-server, -idp, -git, -nostr, -activitypub, -didkey) were version-bumped but never published and stayed at 0.5.0-alpha.1 on crates.io. alpha.3 publishes the whole workspace together, so all seven crates now carry the canonical Multikey + CID work.

Changed

  • W3C CID v1.0 context — the did:nostr DID-document @context[0] migrates from https://w3id.org/did to the W3C Controlled Identifiers v1.0 IRI https://www.w3.org/ns/cid/v1 (render_did_document, did_nostr_document, did_nostr_types docs). This matches the context already emitted by the downstream dreamlab-ai-website resolver, closing a live interop skew where the published pod crates trailed their own consumer. ADR-125 §2.

Added

  • Odd-parity Multikey accept — parse_multibase_schnorr now accepts the odd-y compressed prefix fe70103 (MULTIKEY_PREFIX_ODD) on decode, per the CID guidance that implementations SHOULD handle both parities; at this release even-y fe70102 was the only form produced on encode (superseded in 0.5.0-alpha.10, which adds the controller-published fe70103 form per nostrcg/did-nostr#145). New parse_multibase_accepts_odd_parity round-trip test.

[0.5.0-alpha.2] - 2026-06-15

Partial release: only solid-pod-rs (core) reached crates.io at this version; the sibling crates ship this content under 0.5.0-alpha.3.

Changed

  • Canonical Multikey convergence (did-nostr) — DID-document emission converges on the canonical Multikey verification-method form (publicKeyMultibase: "fe70102<hex>", MULTIKEY_PREFIX/MULTIKEY_LEN), superseding the 2019-suite + publicKeyHex shapes. Adds solid-pod-rs-git::identity write-side DID emission.

[0.5.0-alpha.1] - 2026-06-13

Documentation

  • Complete in-crate Diataxis docs + crate-level rustdoc for the 0.5.0-alpha.0 provenance/economy release (the embedded DOCS_DIR and the //! overviews had no coverage of it). New explanation/ docs for provenance & the trust ledger and for payments & the web ledger; the new /pay/* + _prov routes in reference/http-endpoints.md; the new env vars in reference/env-vars.md; a corrected feature-flag table and JSS-parity rows. No code changes — documentation only; 0.5.0-alpha.0 consumers need not bump.

[0.5.0-alpha.0] - 2026-06-13

The provenance release (ADR-059). Two composable, cost-tiered provenance primitives become first-class, and the JSS-derived payment economy is fully routed. Full workspace: 1542 tests pass; the wasm32 core surface stays pure.

Added

  • git-marks (cheap, always-on): every LDP write to a git-backed pod becomes a git commit, captured as a GitMark and persisted as a PROV-O sidecar (<resource>.prov.ttl), emitted on the Updates-via notification stream. provenance::GitMarker trait + solid-pod-rs-git::ShellGitMarker.
  • block-trails (expensive, opt-in): a Bitcoin taproot-anchored, hash-chained, tamper-evident provenance trail. bitcoin_tx.rs ports the JSS token.js taproot tx-builder byte-for-byte (P2TR, BIP-341 TapSighash, BIP-340 schnorr signing), validated against official BIP-340/341 vectors and a JSS cross-impl golden. MempoolLookup/MempoolBroadcast over the public mempool.space testnet4 API; BlockAnchorer (verify + write). MRC20 is one instance of the general trail.
  • ProvenanceLog composition: git-mark always / Bitcoin anchor opt-in via AnchorPolicy {Never, Always, HighValue, Epoch}; the anchor's state_hash commits to the git commit SHA, binding the two tiers. Epoch batches commit SHAs into one Merkle root anchored by a single tx (one tx notarises many commits). acl:ProvenanceAnchor WAC condition flags anchor-worthy resources.
  • _prov API: GET /{pod}/_prov/{commit_sha} resolves a git-mark to its resource + anchor; POST /{pod}/_prov/anchor (NIP-98, payment-gated) upgrades a git-mark to a Bitcoin anchor.
  • Routed the previously-orphaned web-ledger / order-book / AMM 402 economy: /pay/.balance, /pay/.deposit (TXO + MRC20), /pay/.address, /pay/.offers, /pay/.sell, /pay/.swap, /pay/.pool, /pay/.buy, /pay/.withdraw, /pay/.withdraw-sats. StoragePaymentStore is now the sole ledger I/O path.

Fixed

  • Security: git smart-HTTP routes are now WAC-gated — handle_git enforces Read for clone/fetch and Write for push against the pod ACL, closing the anonymous clone/push hole (a private pod's history was world-clonable).
  • Payment replay protection (check_replay/record_replay) is now wired into the deposit path — a duplicate TXO/MRC20-state deposit can no longer double-credit.
  • verify_mrc20_anchor composes the taproot crypto with a real mempool UTXO lookup (was: lookup left to the caller).

Changed

  • On-demand git auto-init on first push (replaces the prior 404-on-missing-repo).
  • Workspace 0.4.0-alpha.17 → 0.5.0-alpha.0.

[0.4.0-alpha.17] - 2026-06-10

Fixed

  • solid-pod-rs-server could not be published to crates.io: its MCP docs tools embedded the documentation tree via include_dir!("$CARGO_MANIFEST_DIR/../solid-pod-rs/docs"), a path escape that does not exist in a packaged tarball, so cargo publish verification failed. The embedding now lives in the owning crate as solid_pod_rs::DOCS_DIR behind the new embedded-docs feature (re-exporting include_dir), and the server consumes that. Registry builds of every crate in the workspace are now self-contained.

Added

  • solid-pod-rs: embedded-docs feature — embeds the crate's Diataxis docs/ tree as pub static DOCS_DIR (the tree already ships in the published package).

[0.4.0-alpha.16] — 2026-06-09 — version-bump to disambiguate the alpha.15 alias

Version-only release. 0.4.0-alpha.15 had come to alias two distinct code states: the crates.io publish (checksum a53804d0…, consumed by nostr-rust-forum) and git HEAD, which had advanced past the publish without a version bump or tag (the highest real git tag was alpha.11). Downstream consumers pinned to the same version string were building different auth code. This release cuts a real version and the first tag since alpha.11 so every consumer can pin a single, unambiguous code state.

Changed

  • Workspace version 0.4.0-alpha.15 → 0.4.0-alpha.16; all seven member crates and their internal cross-crate path dependency pins advance with it.

Included (post-publish commits now under a real version/tag)

  • WAC ancestor accessTo over-inheritance + git HTTP read-auth bypass fix (75946cf).
  • payments::debit wired into the WAC grant path (f7785d7, R-04) — the resource-cost-accounting fix. The published alpha.15 crate predates this, so it served cost-gated reads without consuming the cost; consumers pinned to the git state get the corrected behaviour.

[0.4.0-alpha.15] — 2026-05-30 — JSS v0.0.204 sync (MCP server, install CLI, NIP-98 minting)

Integrates the upstream JSS changes from 0.0.197 (10bd60f) through 0.0.204 (9d29167): the Model Context Protocol agent surface (#490), the install app-distribution CLI, and two content-type/listing fixes (#531, #533). The JSS comparator pin advances accordingly across PARITY-CHECKLIST.md, GAP-ANALYSIS.md, and the feature inventory. alpha.12–alpha.14 are documented in crates/solid-pod-rs/CHANGELOG.md.

Added

  • MCP server subsystem (solid-pod-rs-server/src/mcp/, JSS #490) — POST /mcp exposes the pod as a Model Context Protocol 2025-03-26 tool surface over the Streamable HTTP transport (JSON-RPC 2.0, single-shot JSON with an SSE upgrade for the streaming subscribe tool). Sixteen tools spanning resource CRUD, ACL read/write, skills, docs, pod info, subscribe, and call_remote_pod. Identity reuses the pod's NIP-98 verifier, so every tool call gets the same WAC treatment as a REST request. Off by default — enabled with --mcp / JSS_MCP, overridable by --no-mcp. call_remote_pod is gated to /private/federation/ for did:nostr identities with a depth-3 recursion cap.
  • install operator subcommand (solid-pod-rs-server/src/cli/install.rs, JSS src/cli/install.js) — clones a Solid app and pushes it into a pod over the git smart protocol. App-spec grammar mirrors JSS (bare name → github.com/solid-apps/<name>, org/repo, full git URLs, with #<ref> pin and =<dest> rename). Dual HEAD:main + HEAD:gh-pages push. Authenticates with a single NIP-98 token minted over the destination repo URL (method *) via git http.extraHeader, or a bearer --token fallback. NIP-98 signing requires the new install cargo feature.
  • NIP-98 token minting (auth::nip98::mint / mint_with_payload, feature nip98-schnorr) — constructs a kind-27235 event, computes the canonical NIP-01 id, and signs it with a deterministic BIP-340 Schnorr signature matching the verify_raw path. Nip98Event now derives Serialize.
  • auth::nip98::MatchPolicy — Strict (every REST/MCP endpoint) and GitLenient (the git push bridge: *-method wildcard + repo-URL-prefix binding, so one static http.extraHeader token covers the multi-request smart protocol). Schnorr is fully verified under both.
  • ldp::guess_content_type (JSS #533 getContentType) — resolves a MIME type for sidecar-absent resources (dotfile rule → Solid RDF / playlist overrides → the mime_guess database → application/octet-stream), so git-extracted app files render inline instead of downloading.

Fixed

  • Symlinked-directory container listing (JSS #531) — FsBackend container listings now reclassify symlinks from their dereferenced stat, so a symlinked directory lists as a container, matching a direct GET.
  • Mashlib audio rendering (JSS #533) — mashlib::should_serve now matches the whole audio/* family rather than enumerating exact spellings.

[0.4.0-alpha.11] — 2026-05-16 — JSS Phase 1 port

Implements the three Phase 1 features shipped by JSS v0.0.190 (May 2026, issue #437). All three feature flags are default-off so existing consumers see no surface change unless they opt in. Type shapes declared in the alpha.10 scaffold (KeyProvisioningOutcome, KeyProvisioningPlan, PodExportBundle, PodExportEntry, ExportOptions) are preserved as ABI — downstream crates (NRF, dreamlab-ai-website) can import unchanged.

Added

  • provision-keys feature (solid-pod-rs-idp, default-off, parity row 196). key_provisioning::provision_pod_keys generates a BIP-340 Schnorr secp256k1 keypair, NIP-19 bech32-encodes npub and nsec (hand-rolled bech32 encoder — no new dependency on bech32), writes /private/privkey.jsonld and an owner-only JSON-LD ACL sibling, and patches the WebID /profile/card JSON-LD island to include the nostr:pubkey triple. Accepts deterministic entropy for test reproducibility. Depends on solid-pod-rs/did-nostr + core's nip98-schnorr + dep:k256. 3 integration tests cover deterministic seeding, ACL evaluator round-trip, and WebID patching idempotency.
  • nip05-endpoint feature (solid-pod-rs + solid-pod-rs-server, default-off, parity row 197). Wires GET /.well-known/nostr.json?name=<local>. Validates the NIP-05 local-part regex, resolves _ → /profile/card and <name> → /<name>/profile/card, extracts nostr:pubkey from the JSON-LD data island via the new webid::extract_nostr_pubkey helper, returns application/json with Access-Control-Allow-Origin: *. 5 actix-test integration tests in solid-pod-rs-server.
  • export-jsonld feature (solid-pod-rs, default-off, parity row 198). export::export_pod_jsonld walks the pod tree, excludes /private/* by default, opt-in via ExportOptions::include_private, base64-encodes each resource body (binary-safe), sorts entries ascending by created (mirrored from modified on backends that don't track creation separately). Bundle envelope carries @context = "https://solid-pod-rs.dev/ns/export/v1". 3 integration tests cover default exclusion, opt-in inclusion, and time-chain ordering.
  • webid::extract_nostr_pubkey helper (always available) — locates the nostr:pubkey triple inside a WebID's JSON-LD data island. Mirrors extract_oidc_issuer semantics; returns Ok(None) when absent.

Changed

  • PARITY-CHECKLIST.md: rows 196–198 promoted from scheduled to present (Phase 1 port). Tally restored to ~98% strict parity (129/135). Row 128 (NIP-05) updated — builder primitives still ship as net-new; the pod-resident server route lands via row 197.
  • solid-pod-rs-idp/Cargo.toml: added bytes to direct dependencies (was transitive through solid-pod-rs); the provision-keys feature flag inherits solid-pod-rs/did-nostr plus dep:k256.

Cross-repo consumer chain

  • NRF (nostr-bbs forum primitive): now upgradable to alpha.11 — the KeyProvisioningOutcome shape is stable; NRF can opt into the provision-keys feature for its registration flow.
  • dreamlab-ai-website: can call export_pod_jsonld via the export-jsonld feature for "download my data" surfaces.

Upgrade path (alpha.10 → alpha.11)

No breaking changes to default-feature builds. Consumers who pinned solid-pod-rs = "0.4.0-alpha.10" bump the patch component; struct literals constructing ProvisionPlan will gain a new optional provision_keys field gated on the provision-keys feature — only visible when the consumer enables that flag.

[BIP-340 Schnorr Fix] - 2026-05-12

Fixed

  • NIP-98 BIP-340 Schnorr pre-hashing mismatch (BREAKING). The Schnorr signature verifier in auth::nip98::verify_schnorr_signature was calling vk.verify() (the k256 Verifier trait method, which applies an extra SHA-256 tagged hash per the k256 Verifier trait semantics) instead of vk.verify_raw() (BIP-340 correct, raw 32-byte message). This caused signature verification to fail against any standard Nostr client that signs per BIP-340 (raw event-id bytes), breaking SSO interoperability with the nostr-bbs relay and forum. The fix switches to verify_raw() for verification and sign_raw() for test fixture signing. The k256 signature::Verifier trait import is no longer used.
  • Test and bench fixtures updated to use sk.sign_raw(&id_bytes, &[0u8; 32]) instead of sk.sign(), matching the BIP-340 raw signing convention: tests/cid_verifier_sprint11.rs, tests/nip98_extended.rs, tests/oidc_integration.rs, benches/nip98_verify_bench.rs.

[Security Audit Sprint] - 2026-05-11

DreamLab ecosystem-wide security audit. 8 fixes applied to solid-pod-rs covering P0 critical, P1 high, P2 medium, and Round 2 P0 findings.

Security

  • P0-07: HTTP signature Date header freshness check enforced at +/-5 minutes in http_sig.rs, rejecting replayed signatures with stale timestamps that previously passed verification indefinitely
  • P0-08: SSRF guard added to actor key resolver in http_sig.rs and new ssrf.rs module, blocking requests to RFC-1918 private addresses, link-local, loopback, and metadata endpoints when resolving remote ActivityPub actor keys
  • P0-09: SSRF guard added to ActivityPub delivery outbound POST in delivery.rs, applying the same private-address blocklist to prevent the server from being used as an HTTP proxy to internal services
  • R2-P0-04: IPv4-compatible IPv6 SSRF bypass fixed in ssrf.rs; addresses like ::ffff:10.0.0.1 and ::ffff:127.0.0.1 now correctly resolve to their IPv4 equivalents before the private-range check
  • R2-P0-05: Same IPv4-in-IPv6 fix applied in the ActivityPub SSRF module (activitypub ssrf.rs), plus 6to4 (2002::/16) address bypass blocked

Fixed

  • P1-25: WAC PATCH operations now require Write permission instead of Append in server lib.rs, matching the Solid Protocol specification where PATCH replaces resource content
  • P1-26: Passkey credential counter updated on successful authentication in passkey.rs, enabling clone detection per the WebAuthn specification
  • P2-09: Notification buffer bounded to 10,000 entries in notifications/mod.rs, preventing unbounded memory growth from slow or disconnected subscribers