All notable changes to solid-pod-rs will be documented in this file.
The format is based on Keep a Changelog.
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).
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 hiddenrefs/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 fromFORGE_CONFIGandFORGE_*environment overrides; the mirror started at boot fromFORGE_MIRROR_RELAYSandFORGE_MIRROR_FOLLOW; featuresforge-mirrorandforge-full;X-Invite-Tokenredeemed at registration;--trust-proxywithcf-visitor; ActivityPub actor knobs (activitypubfeature); 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.
- 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/mirrorhidden 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 seehttps.
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).
solid-pod-rs-forge: repo-held git-mark trails (gitmarkmodule). A repository that commits ablocktrails.json(profilegitmark) now has it read at the default-branch tip and every mark's taproot address re-derived with gitmark's state bytes (SHA-256of the commit hash as text). Served at<forge>/<owner>/<repo>/blocktrails.jsonwithAccess-Control-Allow-Origin: *for the hosted verifier. Address derivation needs theanchoringfeature. Also: per-chain explorer links,anchor_tags(ther,anchor,chain,txoandcommitNIP-34 tags) anddid_short. Differential vectors were generated by running the JSStrailAddressandtrailProgram.solid-pod-rs-forge:did:nostragents are displayed asdid: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 (mirrorfeature, default off). Follow(pubkey, name)pairs on configured relays andgit fetchintorefs/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 atapi/repos/<owner>/<repo>/mirrorahead of the repo-exists check.solid-pod-rs:mrc20::bt_trail_addresses, every link's taproot address in one linear walk.
- CORS
Access-Control-Expose-Headersnow also listsAccept-Query,Last-ModifiedandWWW-Authenticate(JSS PR #629).Accept-Queryis 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
/forgereaches the forge, never the pod git lane'spush_not_served. No behaviour change; the routes were already registered in that order.
- solid-pod-rs-server no longer serves
git push.git-receive-packand itsinfo/refs?service=git-receive-packdiscovery 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; thesolid-pod-rs-gitpush guard is unchanged. Clone and fetch are unaffected.
-
MCP skill reads are charged.
get_skillandget_pod_skillreturned the body of anacl:PaymentCondition-gatedSKILL.mdwithout debiting the caller, so a paid skill was free over MCP. Both now go through the same charging check asread_resource. -
Dependency advisories. rustls 0.23.46+ and quinn-proto 0.11.19, and the unmaintained
serde_yaml(unsafe-libyaml) replaced by the API-compatibleserde_norway. -
One hardened
gitconstructor (solid_pod_rs_git::hardened_git,hardened_git_async,hardened_cgi_env) now backs every productiongitspawn in the git, forge and server crates: no repo-local hooks, no fsmonitor, no system/global config, no terminal prompt. Thegit-http-backendCGI gets the same settings throughGIT_CONFIG_COUNT. -
Solid-OIDC
audis checked.verify_access_tokenrequiresaudto be (or contain)solid;verify_access_token_with_policytakes an explicitAccessTokenPolicy. Akidresolving to a JWK of the wrong key family for the headeralgis rejected. NewWebIdProfileSourceandverify_webid_issuer_bindingcheck that the WebID profile declares the token'sissassolid:oidcIssuer; the caller supplies the profile fetch. -
Dev bearer behind a feature.
verify_dev_bearerexists only with thedev-bearerfeature (off by default) and now requiresaud = solid-pod-rs-dev-bearerand a lifetime of at most 24 h. -
Case-insensitive filesystems. The dotfile guard refuses
doc.ACL,X.META.JSONand similar case variants of guarded names. New publicsolid_pod_rs::security::dotfile::fold_casefolds 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.
- Every production
gitsite, including thegit-http-backendCGI, now ignores system and global config. This drops operatorsafe.directoryentries, andsolid-pod-rs-server installloses credential helpers, proxy settings and terminal prompts (it authenticates with its explicithttp.extraHeader). verify_access_tokenrejects tokens whoseauddoes not namesolid(previously anyaudverified);CachedFetcher::verify_access_token_cached_with_policyis the cached equivalent. Deployments with another audience useverify_access_token_with_policy.- Dev-bearer tokens need
audandexp - iat <= 24 h; build with--features dev-bearerto use them.
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.
COPYrequires read access onSource(the dotfile allowlist applies), rejects relative, query, fragment and cross-host values with 400, and accepts same-host absolute URLs. The source.aclsidecar 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,.metaand.prov.ttlare no longer merged; pay-per-read children are left out; a response with private parts carriesCache-Control: no-storeandVary: 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/(exceptblocktrails.json) requireacl:Controlto 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.jsonand temp-file names via MCP. - Container
index.htmlnegotiation checks Read on theindex.htmlchild, not only the container. - Git push guard.
GitHttpService::with_push_guardrejects, 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 owngitcalls drop repo-local hooks, fsmonitor and global config. - Anchoring no longer swallows storage errors. Only
NotFoundmeans absent; an unreadable trail or epoch file fails the anchor with a recordedanchor_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
rawHexbefore 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.jsongrowth 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: inspectstate.jsonbefore deploying.
GitmarkAnchorer<M>requiresM: MempoolTxPresence(implemented forMempoolHttpClient).POST /_prov/anchorreturns 202{status:"pending"}without refunding when a broadcast outcome is ambiguous; failures before broadcast still refund (502)./pay/.withdrawand/pay/.buyreturn 503 withRetry-Afterwhen 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/nullfor marks drops any operatorsafe.directory.- Docs: removed the non-existent
standaloneexample; usecargo run -p solid-pod-rs-server.
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.
- Per-link trail verification, ported from blocktrails/verify 043e7af
(
solid_pod_rs::blocktrail).walk_blocktrailrecomputes every mark's output key from a trail'spubkeyBaseand 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_blocktrailthen 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) orpartial.verify_anchor_chainruns 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 bygen-verify-trail-vectors.mjsfrom blocktrails/verify 043e7af, blocktrails/git-mark b852d7d and sidestr/spec e8deb63keys.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 isbroken linkhere; the page shows "chain link ✗" but still labels it verified. Blocktrail, the sharedblocktrails.jsontype, in the shape of blocktrails/spec's gitmark profile §5.2 (@type,version,profile,pubkeyBase,chain,states,txoas TXO URIstxo:<chain>:<txid>:<vout>?amount=&commit=), andBlocktrailTxoparsing and formatting TXO URIs as git-mark'sparseTxoUri/formatTxoUrido.provenance::BlocktrailEnvelopeis now an alias of it. Files in the earlier shape (@id,genesis,txoentries{outpoint, blockheight}) still parse and serialise back byte for byte as read.- Git-mark trails for new anchors.
bitcoin_tx::gitmark_genesis/gitmark_advancebuild marks whose state is the commit as 40-hex text with no MRC20 wrapper (40 or 64 lowercase hex accepted);gitmark_advancerefuses 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}.jsonwith the publicblocktrails.jsonbeside them;build_anchoreruses it whenever no MRC20 trail is already issued under the name (default nameprov). bt_address(.., "gitmark")encodes with thegmHRP (git-markNETWORK_HRP);bcandtbare unchanged.mrc20::TxInandTxInfo::vin(the outpoints a transaction spends; the server's mempool client now reads them),BuiltTx::txid, andbitcoin_tx::decode_tx_info.scalar_mod_n(already reducingint(h) mod nsince 0.5.0-alpha.11) pinned against keys.mjstaggedScalar'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.
verify_mrc20_anchorandMempoolBlockAnchorer::verifywalk 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.Mrc20AnchorResultgainsreport;MempoolBlockAnchorer::verify_reportexposes the per-mark report. An anchor now stays verifiable after a later anchor spends it. Issued MRC20 trails, their addresses andanchor_state'surn:mono:op:anchorstates are untouched.- Breaking for struct literals (pre-1.0):
TxInfogainsvin,BuiltTxgainstxid,Mrc20AnchorResultgainsreport;BlocktrailTxois now the TXO URI type andBlocktrailEnvelope::new_gitmark_profileis replaced byBlocktrail::gitmark.
- Blocktrails "the rule as it is" (blocktrails/spec ef54a08), audited and
pinned. The
mrc20chained-key derivation already follows it: the tweak is BIP 341'sTapTweakhash ofx(P) || sha256(state), added to the full point (never its even-y lift; not BIP 341's derivation), the secret chains asd + Σt, and each output key isx(P)itself. A state string is hashed as its UTF-8 bytes, which is blocktrails/verify 043e7af'sstateHash(a string state as its text, an object as its JCS). No arithmetic changed, so no issued MRC20 trail address changes: the pinnedCHAINED_GOLDEN/CHAINED_GOLDEN_JCSrows, stored trails (pubkey_base66-hex,state_stringsas stored) and the anchor flow are untouched. Added:tests/fixtures/blocktrails/keys-walk-vectors.json, generated by the upstream JS (gen-keys-walk-vectors.mjsbeside it: sidestr/spec e8deb63keys.mjsandaddress.mjson bitcoin-desktop/schema b8cbf63), andtests/blocktrails_keys_walk.rsreproducing every point, x-only output, chained secret andtb/bcaddress: theCHAINED_GOLDENinputs, object states as JCS, an03base with an odd-y intermediate, bare-x bases, mixed text and object states, and git-mark's live trail. WithSIDESTR_SIDINGandSCHEMAset the generator also runs live and must reproduce the file.- blocktrails/git-mark b852d7d's live-trail pin (base
0273c7f6…, three commits as text, outputse403de73…,3ef39ed4…,d7abfea9…) and its swapped-commit refusal, as unit tests. bt_base_point: a base key read as keys.mjsbasePointreads it (a compressed point keeps its parity; a bare 64-hex x ordid:nostr:<x>is the even-y02point; a did:nostr Multikeyfe701…). The derivation functions andbt_addressnow accept those forms; 66-hex callers are unchanged.verify_mrc20_anchorstill requires the 66-hex compressed point.bt_normalize_privkey(keys.mjsnormalize: the holder of a bare-x base normalises the secret once),bt_trail_outputsandbt_verify_trail_outputs(blocktrails/verify's per-mark walk: every link checked,mismatch at index i), andbt_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.
- The chaining scalar is
int(TapTweak hash) mod n, zero refused, as the spec and keys.mjstaggedScalarstate. 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.
- The TXO stand-in deposit (ADR-2008 D6).
POST /pay/.depositno longer has a branch that credited(vout + 1) * 1000sats for any parseabletxid: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_uristays as a pure parser (nostr-bbs-pod-worker uses it). This is breaking forsolid-pod-rs-server: the publicAppStatefield and the CLI flag are gone.
-
Bitcoin primitives come from rust-bitcoin (ADR-2008 D1-D2).
bitcoin_txand themrc20anchor module no longer hand-roll any Bitcoin or secp256k1 primitive. Transaction and CompactSize serialisation, txid byte order, P2TR scripts, the BIP-341TapTweakandTapSighash, the key-path secret tweak and BIP-340 signing and verification come frombitcoin0.32.102 and its re-exportedsecp256k10.29 (libsecp256k1). Bech32m addresses come frombitcoin::Address. The hand-written mod-n arithmetic (add_mod_n,neg_mod_n,sub), both tagged-hash helpers and the bech32m encoder are deleted. Themrc20feature now enablesdep:bitcoininstead ofdep: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_tokenandbt_derive_chained_privkeyrequire an exactly 32-byte secret key (a 24-31-byte key used to be left-padded, and inbuild_transactionthen panicked);p2tr_scriptandbt_addressrefuse an x-only key that is not a curve point (a P2TR output to it is unspendable);verify_keypath_signaturerefuses 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):WebLedgerbalances change only through receipts (solidpayorg/teller 7c00cealib/teller.mjs:40-58).WebLedger::creditandWebLedger::debit, andcredit_currency/debit_currency, are nowpub(crate), and theidandentriesfields are crate-private (read them withid()andentries()). The public mutators arecredit_by_outpoint(account, currency, txid, vout, value)(the outpoint is the receipt; a second credit of it returnsReceiptOutcome::AlreadyApplied) anddebit_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, andcharge/refund(Charge)take and return an access fee (per-requestPaymentConditionand the_provanchor 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) andWebLedger::with_genesis:hash = sha256(JCS(genesis)),id = urn:webledgers:<hash>, checked bycheck_genesis(tellercheckLedger). The document gainshash,genesis,deposits,appliedandpayouts;descriptionandcreatedbecome 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):TokenConfiggainsaccepted_issuers(#[serde(default)], so stored configs still load; struct literals need the field) andaccepts_issuer. -
/pay/.buy,/pay/.withdrawand/pay/.withdraw-satsdebit throughdebit_by_payoutkeyed 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/.balanceand/pay/.addressdocs 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/.withdrawand/pay/.withdraw-satsnow answer 400 (an account is a did:nostr identifier) for a principal teller'saccountOfrejects, which includes the https WebID a dev bearer token yields. NIP-98 callers are unaffected. -
CI runs doctests:
cargo test --docper core feature set andcargo test --workspace --doc.--all-targetsskips doctests, so thecompile_failgate keepingWebLedger::credit/debitcrate-private and the teller genesis-hash doctest were not running;tests/ci_doctest_gate.rsfails if the workflow drops them again.
- MRC20 deposits are bound to this pod's token.
POST /pay/.depositnow refuses a trail whoseanchor.pubkeyis not the configuredpay_config.token.issuer(or one of the newaccepted_issuers) with 403, and a state whose ticker is notpay_config.token.tickerwith 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.
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.
- Full-key Multikey encoding (
did_nostr_types, re-exported bysolid-pod-rs-nostr::did):format_multibase_public_key(&k256::PublicKey)andformat_multibase_sec1(&[u8])emitfe70102…/fe70103…by the key's actual parity;render_did_document_published(&k256::PublicKey)renders the controller-published document.parse_multibase_sec1decodes the full point a document carries, parity kept;NostrPubkey::from_public_key/to_even_public_keyconvert between the identifier and points (an identifier denotes the0x02point). Reading and encoding agree withbasePoint()/multikey()in sidestr/spec PR #28, checked against itskeys-vectors.jsonencoding fields. No key-arithmetic (tweak) API is added; that waits for PR #28 to merge. - Upstream conformance vectors.
tests/fixtures/did-nostr/vendorstest-vectors-generated.jsonfrom nostrcg/did-nostr@4ea80d8;tests/did_nostr_vectors.rsrunsdecode_even_parity,decode_odd_parity, the key-transformation, minimal-document and error vectors, plus encode tests for odd-y full keys.
solid-pod-rs-git::write_agent_identitypublishes the controller's parity. When the secret key is supplied,agent.did.jsonis rendered from the full key, so an odd-y key yieldsfe70103…. A secret that is malformed or whose public key does not matchpubkey_hexis 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-typesnow pullsk256(pure Rust, wasm32-safe) for point validation and full-key encoding. - Docs no longer call
0x02invariant.MULTIKEY_PREFIX,format_multibase_schnorr,render_did_document, the interop/WebID notes and the git identity invariants now describe0x02as the identifier-only default and0x03as permitted for controller-published documents.
rustls0.23.45. RUSTSEC-2026-0285 (TLS 1.3 handshake messages accepted across encryption-level boundaries) turned the CIcargo-auditandcargo-denyjobs 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.
- Advisory exceptions re-verified and re-dated (2026-09-21).
RUSTSEC-2026-0258is patched only ath2 >= 0.4.16, a different major line from theh20.3 thatactix-http3.13.3 pulls unconditionally, so there is no in-semver fix to take;actix-webis taken withdefault-features = false, so no HTTP/2 listener is enabled. Rationale corrected and review moved to 2026-12-20 in bothdeny.tomland.cargo/audit.toml. - README told the truth about the audit gates. The status section claimed
cargo audit --deny warningsfails onRUSTSEC-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.
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.
- 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;checkis exactlycheck_at(key, Instant::now()). Tests drive virtual time and no longer sleep, which also fixed a wall-clock flake under tarpaulin (62376e9).
chacha200.10.2. Both0.10.0and0.10.1were yanked upstream; the lockfile now resolves the clean patch, socargo denyandcargo auditpass again (62376e9).- rustdoc intra-doc links. Four links that only fail under
-D rustdoc::broken-intra-doc-linkson stable: a bare variant name inwac/resolver.rs, and three short names in theauth::replaymodule docs, which rustdoc resolves in the parent module's scope becauseauth/mod.rsalso documents thepub moddeclaration (62376e9).
- 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-underis the coverage gate (75287ea).
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.
- S3 storage backend dropped. The
s3-backendfeature, theaws-sdk-s3dependency, theStorageBackendConfig::S3config variant, and theJSS_S3_BUCKET/JSS_S3_REGION/JSS_S3_PREFIXenv vars are gone. Storage is now filesystem or memory only; downstream consumers needing an object store supply their ownStorageimplementation. An unsupportedJSS_STORAGE_TYPE(includings3) 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.
- Filesystem backend is capability-sandboxed (
cap-std).FsBackendnow holds acap_std::fs::Dirhandle rooted at the pod store, so path traversal is prevented at the OS-handle level rather than by string inspection alone.fs-backendpullscap-std. - Atomic quota reservation.
QuotaPolicygains areservemethod 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 existingcheck+recordpair is retained as the non-atomic cooperative discipline;recordandreconcilealso take the per-pod lock now. Newsecurity.default_quota_bytesconfig field. - Observer-atomic payment state. The payment handlers now commit the Web
Ledger, replay set, order book and exchange together as a single
state.jsondocument via oneStorage::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. Addsbase64/hex/hmac/sha2to the server for TXO txid derivation and dev-bearer HMAC auth. - HTTP-signature covered-component validation (ActivityPub). The verifier
now rejects a well-formed
Signatureheader that omits or duplicates a required covered component (SigError::MalformedSignature). - IdP: spoofable header path removed. An
AuthenticatedUserIdextractor 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-backendchild, surfaced asGitError::BackendFailedwithexit_code: None. - Nostr relay limits.
RelayLimits/dispatch_message_with_limitsbound per-connection relay message handling in the WebSocket path.
solid-pod-rs-forgeenablesnip98-schnorr+nip98-replayon its core dependency and makes agent resolution async with replay handling.- Server transport trimmed.
actix-webis taken atdefault-features = false, features = ["macros"];rustls-pemfileis dropped from thetlsfeature.lrumoves0.16 → 0.18. - CI / release hardening. All workflow actions are pinned to commit SHAs;
the core feature matrix is restructured (nested
featuresobject) and the now-removeds3-backendcombination is gone; coverage (tarpaulin) is promoted to a required gate (--fail-under 60,fail_ci_if_error: true);cargo-denyruns against the workspace-root manifest. New security regression tests land for MCP/WAC, payment atomicity, quota enforcement and registration.
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.
- did:nostr
@contextis 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), theinteropmalformed-hex fallback, and across tests, conformance fixtures, and fixture schemas in thenostranddidkeycrates. Both forms expand to the same terms under JSON-LD, so consumers that parse rather than byte-compare are unaffected.
- 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 andWWW-Authenticatechallenge 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, andAccess-Control-Allow-HeadersgainsGit-Protocol(protocol-v2 clients send it). The server-wide CORS middleware now defers to handler-setAccess-Control-Allow-Origin(git/forge surfaces are deliberately*-open), making git CORS identical in wildcard and allowlist deployments; the global preflight header list also gainsGit-Protocol. - IdP issuer slash-normalisation (JSS #524 class) —
ProviderConfig::newnow canonicalises the issuer to its trailing-slash form, so the discovery document'sissuer, the RFC 9207issauthorization-response parameter, and the access-tokenissclaim 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=octDPoP proofs, which had left two provider unit tests failing onmain. The helper now mints real ES256 proofs with an embedded P-256 JWK.
JSS_BODY_LIMITenv alias forJSS_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).
- 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),
lastSubmissionerror 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.
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.
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 intosolid-pod-rs-serverbehind the default-offforgefeature; 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 atomicFsSpineStorewith a two-phase pod-write coordinator, and the Tier-2.5f1.*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/forgeroute notes added to thesolid-pod-rs-servercrate//!docs.
- Supply-chain: two yanked
spinversions cleared (CI/audit gate) — the transitivespincrate appeared twice in the lockfile (0.9.8viaflume/lazy_static,0.10.0viacrc-fast), and both were yanked from crates.io, which failedcargo-deny(error[yanked]) andcargo-audit(2 denied warnings) and blocked the release. Resolved per this repo's own audit policy — in-semver fixes are applied bycargo update, not ignored — by pinning the lockfile to the non-yanked in-semver successorsspin 0.9.9andspin 0.10.1. No source or API change; lockfile only.
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.
-
NIP-98 single-use replay guard (native tier) — the stateless NIP-98 verifier accepts any token within its ±120s timestamp tolerance, so a captured
Authorizationheader could be replayed inside that window. The newauth::replay::Nip98ReplayCache(featurenip98-replay, a bounded process-local LRU keyed on the canonical NIP-01 event id) closes the window:Nip98Verifiednow carries a signature-boundevent_id, andsolid-pod-rs-serverrecords every accepted id in a sharedLazyLock<Nip98ReplayCache>— a re-presented token is treated as unauthenticated and the WAC gate returns 401. TTL and size are tunable viaSOLID_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-schnorrfeature; a build that dropped the feature silently degraded to structural checks alone and would accept any forged pubkey.verify_at_with_policynow callsverify_schnorr_signatureon every path: withnip98-schnorrit verifies for real, and without it the call is a fail-closed stub returningPodError::Unsupported, so a mis-configured build denies authentication rather than granting it. A newconst fn assert_schnorr_verification_enabled(present only undernip98-schnorr) is referenced in const context bysolid-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:originenforcement wired into the request path (F4) — theacl-originevaluator support shipped previously but the server never fed it a request Origin, so origin-restricted ACLs were inert. The server now threads the HTTPOriginheader throughenforce_read_ctx/enforce_write_ctxinto the evaluator'sRequestContext, so an ACL bearingacl:origintriples gates cross-origin reads and writes (CSRF defence). Behaviour is conservative: plain ACLs with noacl:origintriple are unaffected, origin-less requests (server-to-server, git smart-HTTP, curl) are denied only where an ACL explicitly restricts origins, andacl:Controlbypasses the origin gate by design so an owner can always repair a mis-configured ACL from any origin. Note:acl:originis the only WAC 2.0 condition satisfiable end-to-end today — theclient_idandissuerconditions remain unsatisfiable because the server does not yet surface an authenticated OIDC client_id / issuer into theRequestContext; those conditions therefore still evaluate deny.
GET /api/exports/all— JSON-LD time-chain pod export route (parity row 198) —solid-pod-rs-servernow exposes the previously library-onlyexport::export_pod_jsonldwalker over HTTP under theexport-jsonldfeature (default-off, native-only). The route is owner-gated: the caller must holdacl:Controlon the pod root, because the bundle can expose every resource (including/private/*when?include_private=true), bypassing per-resource ACLs. Emitted with theexport::EXPORT_CONTENT_TYPEmedia type.
- 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-idpkey-provisioning docs drop the stale "scaffold /todo!()" wording (the bodies are implemented and tested); the corelib.rsexport section notes the route is native-only.
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.
- W3C CID v1.0 context — the did:nostr DID-document
@context[0]migrates fromhttps://w3id.org/didto the W3C Controlled Identifiers v1.0 IRIhttps://www.w3.org/ns/cid/v1(render_did_document,did_nostr_document,did_nostr_typesdocs). This matches the context already emitted by the downstreamdreamlab-ai-websiteresolver, closing a live interop skew where the published pod crates trailed their own consumer. ADR-125 §2.
- Odd-parity Multikey accept —
parse_multibase_schnorrnow accepts the odd-y compressed prefixfe70103(MULTIKEY_PREFIX_ODD) on decode, per the CID guidance that implementations SHOULD handle both parities; at this release even-yfe70102was the only form produced on encode (superseded in 0.5.0-alpha.10, which adds the controller-publishedfe70103form per nostrcg/did-nostr#145). Newparse_multibase_accepts_odd_parityround-trip test.
Partial release: only
solid-pod-rs(core) reached crates.io at this version; the sibling crates ship this content under0.5.0-alpha.3.
- Canonical Multikey convergence (
did-nostr) — DID-document emission converges on the canonicalMultikeyverification-method form (publicKeyMultibase: "fe70102<hex>",MULTIKEY_PREFIX/MULTIKEY_LEN), superseding the 2019-suite +publicKeyHexshapes. Addssolid-pod-rs-git::identitywrite-side DID emission.
- Complete in-crate Diataxis docs + crate-level rustdoc for the
0.5.0-alpha.0provenance/economy release (the embeddedDOCS_DIRand the//!overviews had no coverage of it). Newexplanation/docs for provenance & the trust ledger and for payments & the web ledger; the new/pay/*+_provroutes inreference/http-endpoints.md; the new env vars inreference/env-vars.md; a corrected feature-flag table and JSS-parity rows. No code changes — documentation only;0.5.0-alpha.0consumers need not bump.
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.
- git-marks (cheap, always-on): every LDP write to a git-backed pod becomes
a git commit, captured as a
GitMarkand persisted as a PROV-O sidecar (<resource>.prov.ttl), emitted on the Updates-via notification stream.provenance::GitMarkertrait +solid-pod-rs-git::ShellGitMarker. - block-trails (expensive, opt-in): a Bitcoin taproot-anchored, hash-chained,
tamper-evident provenance trail.
bitcoin_tx.rsports the JSStoken.jstaproot 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/MempoolBroadcastover 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'sstate_hashcommits 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:ProvenanceAnchorWAC condition flags anchor-worthy resources. _provAPI: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.StoragePaymentStoreis now the sole ledger I/O path.
- Security: git smart-HTTP routes are now WAC-gated —
handle_gitenforces 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_anchorcomposes the taproot crypto with a real mempool UTXO lookup (was: lookup left to the caller).
- 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.
solid-pod-rs-servercould not be published to crates.io: its MCP docs tools embedded the documentation tree viainclude_dir!("$CARGO_MANIFEST_DIR/../solid-pod-rs/docs"), a path escape that does not exist in a packaged tarball, socargo publishverification failed. The embedding now lives in the owning crate assolid_pod_rs::DOCS_DIRbehind the newembedded-docsfeature (re-exportinginclude_dir), and the server consumes that. Registry builds of every crate in the workspace are now self-contained.
solid-pod-rs:embedded-docsfeature — embeds the crate's Diataxisdocs/tree aspub static DOCS_DIR(the tree already ships in the published package).
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.
- Workspace version
0.4.0-alpha.15→0.4.0-alpha.16; all seven member crates and their internal cross-cratepathdependency pins advance with it.
- WAC ancestor
accessToover-inheritance + git HTTP read-auth bypass fix (75946cf). payments::debitwired into the WAC grant path (f7785d7, R-04) — the resource-cost-accounting fix. The publishedalpha.15crate predates this, so it served cost-gated reads without consuming the cost; consumers pinned to the git state get the corrected behaviour.
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.
- MCP server subsystem (
solid-pod-rs-server/src/mcp/, JSS #490) —POST /mcpexposes 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 streamingsubscribetool). Sixteen tools spanning resource CRUD, ACL read/write, skills, docs, pod info, subscribe, andcall_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_podis gated to/private/federation/fordid:nostridentities with a depth-3 recursion cap. installoperator subcommand (solid-pod-rs-server/src/cli/install.rs, JSSsrc/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). DualHEAD:main+HEAD:gh-pagespush. Authenticates with a single NIP-98 token minted over the destination repo URL (method*) via githttp.extraHeader, or a bearer--tokenfallback. NIP-98 signing requires the newinstallcargo feature.- NIP-98 token minting (
auth::nip98::mint/mint_with_payload, featurenip98-schnorr) — constructs a kind-27235 event, computes the canonical NIP-01 id, and signs it with a deterministic BIP-340 Schnorr signature matching theverify_rawpath.Nip98Eventnow derivesSerialize. auth::nip98::MatchPolicy—Strict(every REST/MCP endpoint) andGitLenient(the git push bridge:*-method wildcard + repo-URL-prefix binding, so one statichttp.extraHeadertoken covers the multi-request smart protocol). Schnorr is fully verified under both.ldp::guess_content_type(JSS #533getContentType) — resolves a MIME type for sidecar-absent resources (dotfile rule → Solid RDF / playlist overrides → themime_guessdatabase →application/octet-stream), so git-extracted app files render inline instead of downloading.
- Symlinked-directory container listing (JSS #531) —
FsBackendcontainer 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_servenow matches the wholeaudio/*family rather than enumerating exact spellings.
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.
provision-keysfeature (solid-pod-rs-idp, default-off, parity row 196).key_provisioning::provision_pod_keysgenerates a BIP-340 Schnorr secp256k1 keypair, NIP-19 bech32-encodesnpubandnsec(hand-rolled bech32 encoder — no new dependency onbech32), writes/private/privkey.jsonldand an owner-only JSON-LD ACL sibling, and patches the WebID/profile/cardJSON-LD island to include thenostr:pubkeytriple. Accepts deterministic entropy for test reproducibility. Depends onsolid-pod-rs/did-nostr+ core'snip98-schnorr+dep:k256. 3 integration tests cover deterministic seeding, ACL evaluator round-trip, and WebID patching idempotency.nip05-endpointfeature (solid-pod-rs+solid-pod-rs-server, default-off, parity row 197). WiresGET /.well-known/nostr.json?name=<local>. Validates the NIP-05 local-part regex, resolves_→/profile/cardand<name>→/<name>/profile/card, extractsnostr:pubkeyfrom the JSON-LD data island via the newwebid::extract_nostr_pubkeyhelper, returnsapplication/jsonwithAccess-Control-Allow-Origin: *. 5 actix-test integration tests insolid-pod-rs-server.export-jsonldfeature (solid-pod-rs, default-off, parity row 198).export::export_pod_jsonldwalks the pod tree, excludes/private/*by default, opt-in viaExportOptions::include_private, base64-encodes each resource body (binary-safe), sorts entries ascending bycreated(mirrored frommodifiedon 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_pubkeyhelper (always available) — locates thenostr:pubkeytriple inside a WebID's JSON-LD data island. Mirrorsextract_oidc_issuersemantics; returnsOk(None)when absent.
PARITY-CHECKLIST.md: rows 196–198 promoted fromscheduledtopresent(Phase 1 port). Tally restored to ~98% strict parity (129/135). Row 128 (NIP-05) updated — builder primitives still ship asnet-new; the pod-resident server route lands via row 197.solid-pod-rs-idp/Cargo.toml: addedbytesto direct dependencies (was transitive throughsolid-pod-rs); theprovision-keysfeature flag inheritssolid-pod-rs/did-nostrplusdep:k256.
- NRF (nostr-bbs forum primitive): now upgradable to alpha.11 —
the
KeyProvisioningOutcomeshape is stable; NRF can opt into theprovision-keysfeature for its registration flow. - dreamlab-ai-website: can call
export_pod_jsonldvia theexport-jsonldfeature for "download my data" surfaces.
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.
- NIP-98 BIP-340 Schnorr pre-hashing mismatch (BREAKING). The
Schnorr signature verifier in
auth::nip98::verify_schnorr_signaturewas callingvk.verify()(the k256Verifiertrait method, which applies an extra SHA-256 tagged hash per the k256 Verifier trait semantics) instead ofvk.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 toverify_raw()for verification andsign_raw()for test fixture signing. The k256signature::Verifiertrait import is no longer used. - Test and bench fixtures updated to use
sk.sign_raw(&id_bytes, &[0u8; 32])instead ofsk.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.
DreamLab ecosystem-wide security audit. 8 fixes applied to solid-pod-rs covering P0 critical, P1 high, P2 medium, and Round 2 P0 findings.
- 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
- 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
