diff --git a/.changeset/pre.json b/.changeset/pre.json index af7a5574..f12231fc 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -29,17 +29,27 @@ "@dwk/websub": "0.0.0", "@dwk/conformance-target": "0.0.0", "@dwk/esi": "0.1.0-beta.1", - "@dwk/safe-fetch": "0.1.0-beta.1" + "@dwk/safe-fetch": "0.1.0-beta.1", + "@dwk/mastodon-api": "0.0.0" }, "changesets": [ "activitypub-actor-federation", "activitypub-block-onion-delivery", + "activitypub-consolidate-ssrf-guards", + "activitypub-delivery-metrics-parity", + "activitypub-delivery-outcome-logging", + "activitypub-ensure-column-pragma", "activitypub-event-federation", "activitypub-inbox-forwarding-id", "activitypub-inbox-resolution-off-critical-path", + "activitypub-internal-route-marker", "activitypub-mastodon-4-6", + "activitypub-outbox-scan-cap", + "activitypub-outbox-vote-delivery", + "activitypub-rfc9421-inbox-verification", "activitypub-spec-followups", "atproto-pds-account-status", + "atproto-pds-backfill-byte-budget", "atproto-pds-blob-accounting", "atproto-pds-car-block-cid-verify", "atproto-pds-dagcbor-strict-decode", @@ -61,46 +71,88 @@ "atproto-pds-proto-pollution-hardening", "atproto-pds-safe-fetch", "atproto-pds-secp256k1-signing", + "atproto-pds-stream-get-repo", + "calendar-ical-injection", "calendar-icalendar-jscalendar", + "critical-federation-credential-identity-binding", "dpop-hardening-42", + "dpop-htu-configured-endpoint", "dpop-initial", "dpop-server-nonce-99", "endpoint-observability-seams", + "esi-backpressure", "esi-new-package", + "esi-redaction-headers-byte-cap", + "fediverse-interop-phase-1", + "fediverse-interop-phase-2", + "fediverse-interop-phase-3", "fix-esm-extensionless-imports", "host-meta-discovery-endpoint", "htmlrewriter-link-scanning", + "http-signatures-created-max-age", "http-signatures-digest-cavage-nits", "http-signatures-duplicate-components", "http-signatures-implementation", + "http-signatures-require-body-digest", "indieauth-audience-restriction", "indieauth-hardening-gaps", "indieauth-implementation", + "indieauth-reap-expired-rows", "indieauth-state-required", "initial-scaffold", "injectable-logging", + "jwks-negative-cache", + "lazy-schema-fresh-deploy", "ldn-extraction", "ldn-spec-followups", + "log-envelope-spread-order", + "mastodon-api-phase-1", + "mastodon-api-phase-2", + "mastodon-api-phase-3-fidelity", + "mcp-activitypub-inbox-tool", "mcp-auth-bridge", + "mcp-micropub-publish-tool", + "mcp-microsub-tools", "mcp-protocol-core", + "mcp-solid-pod-tools", + "mcp-webmention-tool", + "media-endpoint-extensions", + "media-nosniff-stored-xss", "metrics-analytics-engine", + "micropub-audience-location-visibility", "micropub-auth-scope-conformance-gaps", + "micropub-authorize-before-media-upload", + "micropub-contacts", "micropub-dual-token-reject", "micropub-h-event-calendar-bridge", "micropub-h-event", "micropub-implementation", "micropub-owner-me-check", + "micropub-q-geo-location-venue", + "micropub-richer-source-filters", + "micropub-source-list-query", + "micropub-stable-extensions", "microsub-drop-redundant-schema", "microsub-read-side", "microsub-safe-fetch-migration", + "microsub-validators-after-insert", "next-beta-iteration", + "oauth-client-store-seam", + "oauth-error-echo-and-challenge-scoping", + "oauth-par-strip-client-credentials", + "oauth-reject-duplicate-params", "oauth-server-building-blocks", "package-metadata-consistency", + "queue-consumer-retry-backoff", "rdf-implementation", + "rdf-jsonld-depth-limit", "rdf-jsonld-torrdf-conformance", "regex-fragility-cleanup", "remotestorage-vault", + "safe-fetch-6to4-teredo", + "safe-fetch-allowed-hosts", "safe-fetch-block-onion", + "safe-fetch-json-content-type-and-redirect-semantics", "safe-fetch-new-package", "security-inbox-body-cap-vc-safe-fetch", "server-conformance-column", @@ -110,8 +162,10 @@ "server-phase4-durable-objects", "server-phase4-solid-pod-notifications", "server-phase5-packaging", + "server-security-headers", "server-self-hosting-host", "solid-pod-access-token-jwt-gaps", + "solid-pod-cleanup-313", "solid-pod-implementation", "solid-pod-jti-replay-gaps", "solid-pod-ldp-negotiation-gaps", @@ -121,13 +175,16 @@ "solid-pod-rdf-event-storage", "solid-pod-remotestorage-nosniff", "solid-pod-slug-acl-escalation", + "solid-pod-strip-forged-contains", "solid-pod-wac-allow-n3-patch", "solid-pod-webdav-door", "store-blob-etag-collision", "store-gc-resurrection-guard", "store-implementation", + "store-preserve-containment-toctou", "store-putblob-precondition-orphan", "store-size-mtime", + "store-staging-key-outbox", "streaming-blob-write-path", "url-object-keyid", "vc-did-web-verifiable-credentials", @@ -136,10 +193,14 @@ "wac-effective-acl-fail-open", "wac-effective-acl-stop-condition", "wac-implementation", + "wac-single-effective-acl", + "webauthn-registration-authorize-hook", "webauthn-relying-party", "webdav-class2-handler", "webdav-copy-move", "webdav-credential-endpoint", + "webdav-pathprefix-segment-boundary", + "webdav-pepper-and-chunked-mkcol", "webdav-protocol-core", "webdav-rfc4918-conformance", "webfinger-discovery-endpoint", @@ -150,6 +211,7 @@ "webmention-spec-compliance-followups", "webmention-ssrf-safe-fetch", "websub-hub", + "websub-per-subscriber-delivery", "websub-safe-fetch-migration", "websub-signature-algorithm", "websub-spec-compliance-followups" diff --git a/packages/activitypub/CHANGELOG.md b/packages/activitypub/CHANGELOG.md index 91aff4a1..a36a8dca 100644 --- a/packages/activitypub/CHANGELOG.md +++ b/packages/activitypub/CHANGELOG.md @@ -1,5 +1,236 @@ # @dwk/activitypub +## 0.1.0-beta.5 + +### Minor Changes + +- d7f90d8: `@dwk/activitypub`'s inbox now verifies RFC 9421 (`Signature`/`Signature-Input`) + HTTP Message Signatures in addition to the legacy draft-cavage profile, auto-detected + per request. Delegates the RFC 9421 wire format and crypto to `@dwk/http-signatures` + (now a real dependency, per issue #59) while keeping the existing draft-cavage + path — and its exact `VerifyFailureReason` vocabulary — unchanged, so no caller + needs to change. Traced from a live conformance run against Fedify (issue #273): + Fedify signs `Follow` with draft-cavage but `Create`/other activities with RFC + 9421, so a target that only understood draft-cavage rejected those deliveries + as `missing_signature`. +- 2870b43: Typed post objects + shaped publish endpoint (fediverse interop phase 1, #274). + + - New `objects.ts`: the canonical `PostInput` shape (`note` / `article` / + `page`, media attachments with alt text, `sensitive`, blurhash, `to`/`cc` + overrides) and pure builders producing correctly-addressed AS2 + `Note`/`Article`/`Page` objects — the content shapes Pixelfed (media notes) + and Lemmy (titled `Page`s) render. + - New owner-gated `POST /publish` endpoint accepting a bare `PostInput` + body (same `publishToken` gate as the outbox seam); `POST /outbox` + stays purely AS2. + - Inbound activities are now stored with nullable `object_type` / `audience` + classification columns (annotation only — the liberal store-and-ignore + behavior for unknown shapes is unchanged). + - Follower rows now record the remote instance's `sharedInbox` alongside the + delivery inbox, enabling future per-instance fan-out batching. + +- 96cc2d3: FEP-1b12 group participation (fediverse interop phase 2, #275) and the + WebFinger client half (#277). + + `@dwk/webfinger` gains `lookup.ts` — the pure client half of RFC 7033: + `parseHandle` (bare / `@user@host` / `!community@host` / `acct:` forms), + `webfingerQueryUrl`, `selectActorLink`, and `resolveHandle` with an injected + `fetch` (the package still makes no network calls of its own). + + `@dwk/activitypub` participates in FEP-1b12 communities: + + - **Follow-target typing (§2.1):** `following` rows record `actor_type`, + `inbox`, and `shared_inbox`, resolved from the actor document off the + critical path; pre-existing rows are lazily backfilled by the alarm tick + (permanent failures mark `Unknown`), so old Group follows qualify without + re-following. Owner-published `Follow` / `Undo(Follow)` now record the + relationship and deliver to the target actor instead of fanning out. + - **Announce unwrapping (§2.2):** an `Announce` from a followed `Group` + wrapping a member activity stores the inner activity attributed to its real + author — deduped by inner id, tagged `relayed_by` + group `audience`. + - **Async two-tier origin verification, on by default (§2.2):** relayed + content (`Create`/`Update`/`Delete`) verifies against its origin on the + next alarm tick; votes (`Like`/`Dislike`) verify in batched sweeps. + `verify_state` tracks `pending → verified`; a refuted row is deleted. + Config: `verifyRelayedObjects: "tiered" (default) | "immediate" | "off"`. + - **Community posting (§2.3):** a shaped post with an `audience` Group is + additionally delivered to the group's inbox (resolved from the alarm when + unknown); the group announces it to members per FEP-1b12. + - **Community discovery (§2.4):** a handle-shaped `audience` + (`!birding@lemmy.ml`) on `POST /publish` resolves to its Group actor + IRI at the stateless front door via the `@dwk/webfinger` helper behind the + SSRF guard. + - **`Dislike`** accepted inbound (stored like `Like`) and publishable + outbound (with `Undo`). + +- 48d56a4: Fediverse interop phase 3 (#276): client wiring. + + `@dwk/micropub` (#278): + + - `syndicateTo` config now also accepts an **async provider**, so target + lists can change at runtime (e.g. followed fediverse communities); + `q=config` / `q=syndicate-to` await it. + - New `fediverse.ts` adapter: `entryToFediversePost` maps an `h-entry` onto + the `POST /publish` wire shape (`photo`/`video`/`audio` → typed + attachments with alt text, `name`+`content` → `article`, plain `content` → + `note`, community target → titled `page` + `audience`), and `syndicateEntry` + delivers to `@dwk/activitypub`'s publish endpoint when `mp-syndicate-to` + names the reserved `fediverse` uid or an advertised community. Failures + are logged per target, never fatal to the post creation. No + `@dwk/activitypub` import — the JSON wire format is the contract. + + `@dwk/activitypub` (#278/#279): + + - `createCommunitySyndicationTargets` — an async `{uid, name}` provider of + accepted `Group` follows (display handles like `!birding@lemmy.ml`), + pluggable straight into micropub's `syndicateTo`; backed by a new + internal-only `__following` DO route. + - MCP tools (v3): `activitypub_publish` (write-scoped, `PostInput` in, + handle-shaped audiences resolved via the SSRF-guarded WebFinger lookup) + and the read-only `activitypub_resolve` (handle → actor IRI + type + + profile basics), beside the existing `activitypub_list_inbox`. + - New `discovery.ts` shared by the front door and the tools: guarded handle + resolution and actor-document dereferencing. + +- 90f1bc6: Phase 2 of the Mastodon-compatible client API (#349): the DO-backed read + surface. `@dwk/activitypub` gains additive internal routes + (`__client/timeline`, `__client/notifications`, `__client/entry`, extended + `__stats`) and one new export, `createActivitypubMastodonApi`, composing + `@dwk/mastodon-api`'s router over them (mirrors the `createSolidPodWebdav` + precedent). `@dwk/mastodon-api` gains `GET /api/v1/timelines/home`, `GET +/api/v1/notifications`, `GET /api/v1/statuses/:id`, `GET +/api/v1/accounts/:id`, Mastodon-shaped snowflake IDs, RFC 8288 `Link` + pagination, an allowlist HTML sanitizer for inbound status content, and the + AS2 → Mastodon entity mapping (including FEP-1b12 reblog provenance for + group-relayed posts). Remote account ids are a reversible encoding of the + actor IRI, so `accounts/:id` resolves them with no backend call and no + outbound fetch. Follow notifications are deferred to phase 3 (#350) — + inbound `Follow` activities aren't currently stored in a form this read + surface can classify; see the phase-2 implementation notes for why. +- 1c179ac: Hydrate remote Mastodon client accounts from an alarm-driven ActivityPub actor + cache, include the owner's outbox posts in the home timeline, and expose + stored reply, favourite, and reblog counts on statuses. Outbox timeline IDs + use the snowflake source bit, preserving existing inbox IDs and marker + positions. +- 3a60a5c: Add `createActivitypubMcpTools` (#262): a `@dwk/mcp` tool contribution + exposing the read-only `activitypub_list_inbox`, listing this actor's + received activities newest-first. The public `/inbox` route stays + write-only to peers (ActivityPub §7.1), so this reads through a new + internal-only Durable Object route (`__inbox`, parallel to the existing + `__stats`/`__resolve`/`__deliver` routes) rather than reusing any existing + HTTP surface. `forwardedConfig` is now exported from `handler.ts` so the + tool factory can build the same internal request shape the front door + sends, without duplicating it. + +### Patch Changes + +- e6fee8e: Consolidate the outbound-delivery SSRF guard onto `@dwk/safe-fetch`'s + `assertPublicUrl` instead of a second, hand-rolled IPv4/IPv6 check, closing a + bypass where a mapped, 6to4, or Teredo IPv6 address (e.g. `[::ffff:127.0.0.1]`) + was not recognized as private. The Durable Object's `#resolveInbox` and + `#processVerifications` fetches now route through `safeFetch` as well, so a + redirect on an already-validated target is re-validated hop by hop instead of + trusting the initial check alone. +- a722a2e: Give alarm-driven delivery outcomes `Metrics` counter parity with their log + lines (issue #336). The Durable Object cannot call the injected `Metrics` + seam across the isolate boundary, and — unlike logging, where `console` is a + direct `wrangler tail`-visible escape hatch — there is no direct-to-metrics + equivalent. `activitypub.delivery.succeeded` / `.failed` / `.blocked` counter + deltas now accumulate durably in the DO's SQLite (coalesced by + `(event, fields)`, cardinality-capped with an explicit + `activitypub.metrics.overflow` counter) and are drained to the front door on + the next forwarded request via an internal `x-ap-metrics` response header; + the front door replays them into the injected `Metrics` and strips the + header. Drains are opt-in per request (`x-ap-metrics-drain`) so internal + callers that would not relay the header never consume deltas, and are + bounded per response so a backlog spreads across requests instead of + bursting. +- 8e5ac84: Emit `activitypub.delivery.*` log events for alarm-driven outbound delivery + (and pending-accept inbox resolution) directly via `console.log`/ + `console.error`, visible in `wrangler tail`. Previously these events were + defined in the log vocabulary but never emitted anywhere — an alarm-driven + delivery attempt has no HTTP response to hang the front door's + `x-ap-outcome` header off, so success, retryable failure, permanent failure, + and SSRF-blocked targets were completely unobservable in production. Each + line reports the target host, HTTP status, attempt count, and whether the + row was dropped — never activity bodies, keys, or tokens. +- 3e505be: `#ensureColumn`'s migration-detection now checks `PRAGMA table_info` instead + of swallowing an `ALTER TABLE` error by matching `"duplicate column"` in its + message, matching `@dwk/store`'s existing pattern. The substring match was + fragile — a driver or SQLite version that phrases the error differently would + have silently swallowed a real failure instead of surfacing it. +- 36a3be1: Gate the owner-only internal Durable Object routes (`__inbox`, `__following`) + behind an explicit internal marker header (#310). These routes have no public + front-door equivalent, but the DO served them on path match to any request + carrying the front-door config header — so a future front-door route that + forwarded such a path could expose the owner's inbox. The trusted callers (the + `@dwk/mcp` tool and the community-syndication provider) now set an + `x-ap-internal` marker, and the DO refuses those routes with `404` without it — + defense in depth, mirroring `@dwk/solid-pod`'s internal-route markers. +- 0e65ce3: Cap the number of batches scanned per client-list page — both the outbox + owner-post merge into a Mastodon timeline and the inbox notifications scan — + so a like/announce-dominated outbox or a plain-post-dominated inbox can no + longer force a near-full-table scan per request. Also de-duplicate the + cancellable timeout-signal helper: `@dwk/safe-fetch` now exports + `createTimeoutSignal`, reused by `@dwk/activitypub` and `@dwk/webfinger` + instead of each carrying its own copy. +- 35830e6: Fix: an owner-published `Like`/`Dislike` sent via `POST /outbox` (a + Lemmy vote) only ever reached the actor's own followers — it never reached + the community or post being voted on, since a vote's `object` names content, + not an actor, so there was no inbox to route to the way `Follow`'s `object` + (an actor) already gets single-target delivery. The raw outbox now also + delivers to a named `audience` Group's inbox, the same mechanism community + posts (`POST /publish`) already use. A vote must set `audience` to + reach the community; without it, delivery is unchanged (followers only). +- bde0341: Close two critical identity-binding gaps found in the pre-1.0 code review, both + on unauthenticated / attacker-controlled paths: + + - **`@dwk/activitypub`: actor impersonation via the default key resolver + (#287, #288).** Inbound HTTP-signature verification trusted the `owner` + field of whatever document the attacker-supplied `keyId` served, so a key + document hosted at `https://evil.example/key` could declare + `owner: https://victim.example/users/alice` and have signed activities + attributed to the victim. The default resolver now binds the resolved + `owner` to the origin that **actually served** the key — the final URL after + any redirects, not the requested `keyId`, so an open redirect on the + requested origin cannot smuggle attacker-served content in under it — and the + `keyId` fetch runs through `@dwk/safe-fetch`'s + `safeFetch` — `https:`-only, with private/loopback/link-local hosts blocked + and **every redirect hop re-validated** (so a public host cannot `302` the + fetch onto an internal address), plaintext `http:` no longer accepted, and a + bounded, size-capped body read — instead of an unguarded `fetch`. + + - **`@dwk/vc`: credential forgery via unbound `verificationMethod` (#289).** + Proof verification never tied the proof's `verificationMethod` to the + credential's `issuer`, so a credential naming any `issuer` could be signed + with an attacker's own key and still verify. `verifySingleProof` now + requires the verification method's controller (its declared `controller`, + or the DID/URL portion of the method id) to equal the credential's issuer + before the key is trusted. A new optional `expectedController` on + `VerifyProofOptions` allows overriding the bound party for non-issuance + proof purposes (e.g. a presentation's `authentication` proof). + +- Updated dependencies [0e65ce3] +- Updated dependencies [d7f90d8] +- Updated dependencies [36a3be1] +- Updated dependencies [96cc2d3] +- Updated dependencies [bde0341] +- Updated dependencies [3e505be] +- Updated dependencies [3e505be] +- Updated dependencies [7b4349c] +- Updated dependencies [90f1bc6] +- Updated dependencies [1c179ac] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/webfinger@0.1.0-beta.4 + - @dwk/http-signatures@0.1.0-beta.3 + - @dwk/calendar@0.1.0-beta.2 + - @dwk/log@0.1.0-beta.4 + - @dwk/mastodon-api@0.1.0-beta.0 + - @dwk/ldn@0.1.0-beta.3 + ## 0.1.0-beta.4 ### Minor Changes diff --git a/packages/activitypub/package.json b/packages/activitypub/package.json index b79551a1..9ab95557 100644 --- a/packages/activitypub/package.json +++ b/packages/activitypub/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/activitypub", - "version": "0.1.0-beta.4", + "version": "0.1.0-beta.5", "description": "Edge-native ActivityPub actor: inbox/outbox, follower collections, signed S2S federation. Ships the per-actor Durable Object.", "keywords": [ "activitypub", diff --git a/packages/atproto-pds/CHANGELOG.md b/packages/atproto-pds/CHANGELOG.md index b299dfa6..c33c41fc 100644 --- a/packages/atproto-pds/CHANGELOG.md +++ b/packages/atproto-pds/CHANGELOG.md @@ -1,5 +1,53 @@ # @dwk/atproto-pds +## 0.1.0-beta.2 + +### Minor Changes + +- 39f6d61: Add a composer-injected local-dev SSRF allowlist (issue #257): `@dwk/safe-fetch` gains `allowedHosts` — exact `host[:port]` entries exempted from the private/loopback host block, with every use logged/counted as `safe_fetch.ssrf.allowed_host` — and the consuming packages expose it as `fetchAllowedHosts` in their options/config (webmention verify/discovery/send, websub verify/denial/distribute, microsub feed discovery/fetch, vc did:web resolution + status-list fetch, atproto-pds PLC directory + DID resolution). Deny-by-default is unchanged; scheme checks, redirect re-validation, timeouts, and body caps still apply to allowlisted hosts. This unblocks local `wrangler dev --local` debugging against the local dev site (Anglesite-app#708). + +### Patch Changes + +- 3e505be: The `subscribeRepos` `?cursor=` backfill replay now enforces a 16 MiB total + byte budget, closing the connection with a `BackfillOverflow` error frame if + exceeded, instead of queuing an unbounded burst (up to ~1 GiB in the worst + case) onto the socket with no flow control. Workers' `WebSocket` exposes no + `bufferedAmount` to gate on, and the replay must stay synchronous (no + `await`) to preserve its no-concurrent-commit-interleaving guarantee, so the + fix bounds the total instead of waiting for the client to drain. The matching + rows are now iterated directly off the SQL cursor rather than `.toArray()`'d + up front, so the byte budget also gates further reads from SQLite (not just + sends to the socket) — the initial fix only bounded what reached the wire, + still risking the DO's memory budget while building the full row set. +- e06db4f: Stream `com.atproto.sync.getRepo` (and the `tooBig` firehose fallback it backs) + instead of buffering the whole repository CAR in the Durable Object, closing + #296. `#getRepo` previously decoded every record body up front via `#entries()` + and concatenated the entire CAR into one `Uint8Array` response body — a large + account could overrun the DO's 128 MB memory limit, and every Relay/AppView + full sync hit this path. `#getRepo` now builds its MST entries without decoding + record bodies (`#mstEntries()`), and `car.ts`'s new `writeCarStream` returns a + `ReadableStream` that encodes and enqueues one block at a time as the response + is read, decoding at most one record body from the SQL cursor per pull instead + of the whole repository at once. +- 36a3be1: Stop a client-controlled `Content-Type` on a served blob from becoming stored + XSS (#299). Both packages serve public, unauthenticated blobs whose content type + comes from the (client-controlled) upload, so an uploaded `text/html` (or + `image/svg+xml`) would render as active content on the deployment's own origin — + `@dwk/micropub`'s `media`-scope-only endpoint could thereby escalate to + origin-level script execution. The serve paths now always send + `X-Content-Type-Options: nosniff`, and only serve a known safe media type + (image/video/audio) inline; anything else is served as an opaque + `application/octet-stream` with `Content-Disposition: attachment`, so it + downloads instead of executing. (Note that `nosniff` alone would not stop an + explicit `text/html`, hence the inline allow-list.) +- Updated dependencies [0e65ce3] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.1 ### Minor Changes diff --git a/packages/atproto-pds/package.json b/packages/atproto-pds/package.json index df61dcd0..f4c04c6c 100644 --- a/packages/atproto-pds/package.json +++ b/packages/atproto-pds/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/atproto-pds", - "version": "0.1.0-beta.1", + "version": "0.1.0-beta.2", "description": "Edge-native AT Protocol Personal Data Server: XRPC surface, signed Merkle Search Tree repository, CAR sync, did:web identity. Ships the per-account Durable Object.", "keywords": [ "atproto", diff --git a/packages/calendar/CHANGELOG.md b/packages/calendar/CHANGELOG.md index d9300ed3..6cbf512a 100644 --- a/packages/calendar/CHANGELOG.md +++ b/packages/calendar/CHANGELOG.md @@ -1,5 +1,19 @@ # @dwk/calendar +## 0.1.0-beta.2 + +### Patch Changes + +- 36a3be1: Validate the non-TEXT iCalendar fields that are emitted verbatim, closing a + content-line injection (#306). `DURATION`, `URL`, the `TZID` parameter, and + `REFRESH-INTERVAL` were interpolated into content lines without escaping (unlike + TEXT values, which `escapeText` handles), so user-derived content flowing into a + `CalendarEvent` (e.g. an `h-event`) could smuggle CR/LF and inject arbitrary + properties into a subscriber's calendar. The serializer now rejects a `timeZone` + that is not a well-formed IANA identifier, a malformed `DURATION`/ + `refreshInterval`, and any `URL` carrying a control character; the feed handler + rejects a `filename` that would break the quoted `Content-Disposition` value. + ## 0.1.0-beta.1 ### Minor Changes diff --git a/packages/calendar/package.json b/packages/calendar/package.json index dc221993..81575f7a 100644 --- a/packages/calendar/package.json +++ b/packages/calendar/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/calendar", - "version": "0.1.0-beta.1", + "version": "0.1.0-beta.2", "description": "Canonical JSCalendar (RFC 8984) event model with an RFC 5545 iCalendar (.ics) serializer and a stateless text/calendar feed handler for webcal:// subscription.", "keywords": [ "icalendar", diff --git a/packages/conformance-target/CHANGELOG.md b/packages/conformance-target/CHANGELOG.md index e54a2aa5..a3f9cb53 100644 --- a/packages/conformance-target/CHANGELOG.md +++ b/packages/conformance-target/CHANGELOG.md @@ -1,5 +1,69 @@ # @dwk/conformance-target +## 0.0.1-beta.1 + +### Patch Changes + +- Updated dependencies [e6fee8e] +- Updated dependencies [a722a2e] +- Updated dependencies [8e5ac84] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [0e65ce3] +- Updated dependencies [35830e6] +- Updated dependencies [d7f90d8] +- Updated dependencies [3e505be] +- Updated dependencies [e06db4f] +- Updated dependencies [bde0341] +- Updated dependencies [36a3be1] +- Updated dependencies [2870b43] +- Updated dependencies [96cc2d3] +- Updated dependencies [48d56a4] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [bde0341] +- Updated dependencies [7b4349c] +- Updated dependencies [90f1bc6] +- Updated dependencies [1c179ac] +- Updated dependencies [3a60a5c] +- Updated dependencies [04e16c2] +- Updated dependencies [04e16c2] +- Updated dependencies [3a60a5c] +- Updated dependencies [04e16c2] +- Updated dependencies [8642346] +- Updated dependencies [36a3be1] +- Updated dependencies [193de8d] +- Updated dependencies [bde0341] +- Updated dependencies [2d594d1] +- Updated dependencies [31f95fd] +- Updated dependencies [96105d1] +- Updated dependencies [3b55292] +- Updated dependencies [99a2146] +- Updated dependencies [36a3be1] +- Updated dependencies [3e505be] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] +- Updated dependencies [52c3f4f] +- Updated dependencies [9c3f652] +- Updated dependencies [3e505be] +- Updated dependencies [bde0341] +- Updated dependencies [3e505be] +- Updated dependencies [2de1ea6] + - @dwk/activitypub@0.1.0-beta.5 + - @dwk/webfinger@0.1.0-beta.4 + - @dwk/atproto-pds@0.1.0-beta.2 + - @dwk/vc@0.1.0-beta.4 + - @dwk/micropub@0.1.0-beta.4 + - @dwk/microsub@0.1.0-beta.4 + - @dwk/indieauth@0.1.0-beta.4 + - @dwk/solid-pod@0.1.0-beta.4 + - @dwk/remotestorage@0.1.0-beta.4 + - @dwk/mastodon-api@0.1.0-beta.0 + - @dwk/webmention@0.1.0-beta.4 + - @dwk/websub@0.1.0-beta.4 + - @dwk/webauthn@0.1.0-beta.4 + - @dwk/host-meta@0.1.0-beta.4 + ## 0.0.1-beta.0 ### Patch Changes diff --git a/packages/conformance-target/package.json b/packages/conformance-target/package.json index e8e896b5..5ceb00e1 100644 --- a/packages/conformance-target/package.json +++ b/packages/conformance-target/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/conformance-target", - "version": "0.0.1-beta.0", + "version": "0.0.1-beta.1", "private": true, "description": "Conformance-target Worker: every @dwk endpoint package composed into one deployable Worker (conformance.dwk.io) that the hosted conformance suites run against. Never published.", "type": "module", diff --git a/packages/esi/CHANGELOG.md b/packages/esi/CHANGELOG.md index 307a8695..eed72c76 100644 --- a/packages/esi/CHANGELOG.md +++ b/packages/esi/CHANGELOG.md @@ -1,5 +1,47 @@ # @dwk/esi +## 0.1.0-beta.3 + +### Minor Changes + +- dec2fbe: Propagate backpressure in the ESI transform stream (#307). `transform` scheduled + each output chunk onto the ordered `tail` promise chain but returned + synchronously, so while a slow head-of-line `` fragment fetch (up to + the fragment timeout) held up the tail, the rest of the origin body kept being + pulled and buffered as pending output — unbounded, against the Worker's 128 MB + limit. `transform` now stops accepting input once more than `maxBufferedChunks` + (default 256, configurable) output chunks are scheduled but not yet emitted, + draining the tail before continuing. Fragment-fetch concurrency and output + ordering are unchanged. + +### Patch Changes + +- 3e505be: Fragment failure logs (`esi.fragment.failed`, `esi.include.dropped_max_includes`) + now record only the host of a `src`/`alt` URL (via `@dwk/log`'s `hostFromUrl`) + instead of the full URL, matching every other package's redaction rule for + attacker- or user-supplied URLs. + + `processEsi` now strips `ETag` and `Last-Modified` from the transformed + response alongside `Content-Length` — none of the three still describe the + transformed body, and carrying `ETag`/`Last-Modified` through unchanged could + serve a stale cached representation on a client's conditional request. + + The tokenizer's pending-tag buffer cap (`MAX_PENDING_BYTES`, 8192) is now + measured by UTF-8 byte length instead of the JS string's own `.length` + (UTF-16 code units) — a multi-byte-heavy payload (CJK text, emoji) could + previously grow the buffer well past the intended byte budget before the cap + noticed. + + Added `spec/packages/esi.md`, the only package previously missing one. + +- Updated dependencies [0e65ce3] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.2 ### Minor Changes diff --git a/packages/esi/package.json b/packages/esi/package.json index fb2ed01b..5293bfbb 100644 --- a/packages/esi/package.json +++ b/packages/esi/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/esi", - "version": "0.1.0-beta.2", + "version": "0.1.0-beta.3", "description": "Streaming Edge Side Includes (ESI) processor for Cloudflare Workers responses. Cross-standard reusable; no Workers runtime dependency.", "keywords": [ "esi", diff --git a/packages/host-meta/CHANGELOG.md b/packages/host-meta/CHANGELOG.md index d19a3dc5..5aa37b3a 100644 --- a/packages/host-meta/CHANGELOG.md +++ b/packages/host-meta/CHANGELOG.md @@ -1,5 +1,15 @@ # @dwk/host-meta +## 0.1.0-beta.4 + +### Patch Changes + +- Updated dependencies [0e65ce3] +- Updated dependencies [96cc2d3] +- Updated dependencies [3e505be] + - @dwk/webfinger@0.1.0-beta.4 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/host-meta/package.json b/packages/host-meta/package.json index e373b657..dd5d495e 100644 --- a/packages/host-meta/package.json +++ b/packages/host-meta/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/host-meta", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "Web Host Metadata (RFC 6415) host-meta discovery at /.well-known/host-meta with XRD/JRD content negotiation.", "keywords": [ "host-meta", diff --git a/packages/http-signatures/CHANGELOG.md b/packages/http-signatures/CHANGELOG.md index 77b3640c..63272e44 100644 --- a/packages/http-signatures/CHANGELOG.md +++ b/packages/http-signatures/CHANGELOG.md @@ -1,5 +1,34 @@ # @dwk/http-signatures +## 0.1.0-beta.3 + +### Patch Changes + +- d7f90d8: `@dwk/activitypub`'s inbox now verifies RFC 9421 (`Signature`/`Signature-Input`) + HTTP Message Signatures in addition to the legacy draft-cavage profile, auto-detected + per request. Delegates the RFC 9421 wire format and crypto to `@dwk/http-signatures` + (now a real dependency, per issue #59) while keeping the existing draft-cavage + path — and its exact `VerifyFailureReason` vocabulary — unchanged, so no caller + needs to change. Traced from a live conformance run against Fedify (issue #273): + Fedify signs `Follow` with draft-cavage but `Create`/other activities with RFC + 9421, so a target that only understood draft-cavage rejected those deliveries + as `missing_signature`. +- bde0341: Bound the age of a signature's `created` timestamp so a captured proof without + an `expires` cannot be replayed indefinitely (#294). Verification previously + checked only the future direction of `created` (`created > now + tolerance`); + there was no lower bound, so a signature that carried `created` but no `expires` + (common for draft-cavage) stayed valid forever. Both the RFC 9421 and cavage + verifiers now reject a `created` older than `now - maxAgeSeconds` (allowing + `toleranceSeconds` of skew) with the new `created_stale` reason. `maxAgeSeconds` + defaults to 3600 (one hour) and is configurable; pass `Infinity` to disable. The + bound applies only when `created` is present. +- 3e505be: `verifyMessage` gains a `requireBodyDigest` option: when a `body` is supplied + and the signature covers neither `content-digest` nor `digest`, verification + now fails with `body_digest_required` instead of silently returning + `valid: true` with the body's integrity never actually checked. Off by + default — a caller that intentionally verifies headers only is not forced to + opt out. + ## 0.1.0-beta.2 ### Patch Changes diff --git a/packages/http-signatures/package.json b/packages/http-signatures/package.json index fb78ad70..aa255310 100644 --- a/packages/http-signatures/package.json +++ b/packages/http-signatures/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/http-signatures", - "version": "0.1.0-beta.2", + "version": "0.1.0-beta.3", "description": "HTTP Message Signatures (RFC 9421) and legacy draft-cavage sign/verify. Cross-standard reusable; no Workers runtime dependency.", "keywords": [ "http-signatures", diff --git a/packages/indieauth/CHANGELOG.md b/packages/indieauth/CHANGELOG.md index 28963fbf..8ea9a64b 100644 --- a/packages/indieauth/CHANGELOG.md +++ b/packages/indieauth/CHANGELOG.md @@ -1,5 +1,34 @@ # @dwk/indieauth +## 0.1.0-beta.4 + +### Patch Changes + +- 3e505be: `authorization_codes` and `access_tokens` are now opportunistically reaped of + expired rows on every new authorization-code save / token issuance, since + this package has no cron entrypoint of its own to schedule the cleanup. + Previously neither table was ever pruned, so both grew unbounded over a + deployment's lifetime and slowed `isTokenActive`'s scan. Both tables now also + have a supporting index on `expires_at`, so the new per-write prune is an + index-range delete rather than a full-table scan on every save/issuance. +- bde0341: Create D1 schema lazily so a fresh deploy no longer 500s (#291, #292). The + IndieAuth code/token store, the Micropub post store, and the Micropub/Microsub + DPoP replay stores previously created their tables only in an `init()` that no + handler ever called, so a consumer composing these packages against a brand-new + D1 hit `no such table` on the first authorization/token/publish request, and — + because DPoP replay-checking is on by default — every authenticated + create/update/delete `500`ed permanently. + + Each store now materialises its schema lazily on first use (the same + `ensureSchema` pattern the webmention/websub/microsub stores already use), with + the cached init promise cleared on failure so a transient D1 error doesn't wedge + the store. The IndieAuth RFC 8707 `resource`-column migration now runs on that + lazy path too, so it actually reaches consumer databases. No separate migration + step is required. + +- Updated dependencies [3e505be] + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/indieauth/package.json b/packages/indieauth/package.json index 1aac0e13..4ba96206 100644 --- a/packages/indieauth/package.json +++ b/packages/indieauth/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/indieauth", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "IndieAuth authorization, token, and metadata endpoints with PKCE and DPoP-bound tokens.", "keywords": [ "indieauth", diff --git a/packages/ldn/CHANGELOG.md b/packages/ldn/CHANGELOG.md index 3ee84ea1..b973fc77 100644 --- a/packages/ldn/CHANGELOG.md +++ b/packages/ldn/CHANGELOG.md @@ -1,5 +1,12 @@ # @dwk/ldn +## 0.1.0-beta.3 + +### Patch Changes + +- Updated dependencies [3e505be] + - @dwk/rdf@0.1.0-beta.3 + ## 0.1.0-beta.2 ### Patch Changes diff --git a/packages/ldn/package.json b/packages/ldn/package.json index 55d05c8b..aa5ac741 100644 --- a/packages/ldn/package.json +++ b/packages/ldn/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/ldn", - "version": "0.1.0-beta.2", + "version": "0.1.0-beta.3", "description": "Linked Data Notifications (W3C LDN) primitives: inbox discovery, notification validation, and listing. RDF-only, protocol-agnostic.", "keywords": [ "linked-data-notifications", diff --git a/packages/log/CHANGELOG.md b/packages/log/CHANGELOG.md index 3c065a45..e22c017a 100644 --- a/packages/log/CHANGELOG.md +++ b/packages/log/CHANGELOG.md @@ -1,5 +1,13 @@ # @dwk/log +## 0.1.0-beta.4 + +### Patch Changes + +- 3e505be: `consoleLogger` now spreads the `level`/`event`/`time` envelope last, so a + caller's `fields` (or a composer's `base`) can no longer clobber it by + coincidentally using one of those names as a field key. + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/log/package.json b/packages/log/package.json index a74042f7..cb2327d0 100644 --- a/packages/log/package.json +++ b/packages/log/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/log", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "Minimal injectable structured-logging and metrics seam. Cross-standard reusable; protocol-agnostic, no Workers runtime dependency.", "keywords": [ "logging", diff --git a/packages/mastodon-api/CHANGELOG.md b/packages/mastodon-api/CHANGELOG.md new file mode 100644 index 00000000..0b7b15ad --- /dev/null +++ b/packages/mastodon-api/CHANGELOG.md @@ -0,0 +1,42 @@ +# @dwk/mastodon-api + +## 0.1.0-beta.0 + +### Minor Changes + +- 7b4349c: Add `@dwk/mastodon-api` — phase 1 of the Mastodon-compatible client API + (spec/mastodon-client-api.md, #348): app registration (`POST /api/v1/apps`), + Mastodon-shaped OAuth (`/oauth/authorize`, `/oauth/token` with + `authorization_code` + `client_credentials`, `/oauth/revoke`), instance + documents (v1 + v2), `verify_credentials` (apps + accounts), marker + persistence, and the valid-but-empty stub roster. Opaque SHA-256-hashed + bearer tokens in D1 (`AUTH_DB`) are the documented exception to the + DPoP-everywhere rule: read-only surface, isolated audience, revocable. +- 90f1bc6: Phase 2 of the Mastodon-compatible client API (#349): the DO-backed read + surface. `@dwk/activitypub` gains additive internal routes + (`__client/timeline`, `__client/notifications`, `__client/entry`, extended + `__stats`) and one new export, `createActivitypubMastodonApi`, composing + `@dwk/mastodon-api`'s router over them (mirrors the `createSolidPodWebdav` + precedent). `@dwk/mastodon-api` gains `GET /api/v1/timelines/home`, `GET +/api/v1/notifications`, `GET /api/v1/statuses/:id`, `GET +/api/v1/accounts/:id`, Mastodon-shaped snowflake IDs, RFC 8288 `Link` + pagination, an allowlist HTML sanitizer for inbound status content, and the + AS2 → Mastodon entity mapping (including FEP-1b12 reblog provenance for + group-relayed posts). Remote account ids are a reversible encoding of the + actor IRI, so `accounts/:id` resolves them with no backend call and no + outbound fetch. Follow notifications are deferred to phase 3 (#350) — + inbound `Follow` activities aren't currently stored in a form this read + surface can classify; see the phase-2 implementation notes for why. +- 1c179ac: Hydrate remote Mastodon client accounts from an alarm-driven ActivityPub actor + cache, include the owner's outbox posts in the home timeline, and expose + stored reply, favourite, and reblog counts on statuses. Outbox timeline IDs + use the snowflake source bit, preserving existing inbox IDs and marker + positions. + +### Patch Changes + +- Updated dependencies [7b4349c] +- Updated dependencies [3e505be] +- Updated dependencies [bde0341] +- Updated dependencies [36a3be1] + - @dwk/oauth@0.1.0-beta.4 diff --git a/packages/mastodon-api/package.json b/packages/mastodon-api/package.json index d7ca60eb..1865b0d5 100644 --- a/packages/mastodon-api/package.json +++ b/packages/mastodon-api/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/mastodon-api", - "version": "0.0.0", + "version": "0.1.0-beta.0", "description": "Mastodon-compatible client API subset: app OAuth login, instance metadata, and read-only account/timeline endpoints for off-the-shelf fediverse clients.", "keywords": [ "mastodon", diff --git a/packages/micropub/CHANGELOG.md b/packages/micropub/CHANGELOG.md index 3c383cc4..3d18a09b 100644 --- a/packages/micropub/CHANGELOG.md +++ b/packages/micropub/CHANGELOG.md @@ -1,5 +1,164 @@ # @dwk/micropub +## 0.1.0-beta.4 + +### Minor Changes + +- 48d56a4: Fediverse interop phase 3 (#276): client wiring. + + `@dwk/micropub` (#278): + + - `syndicateTo` config now also accepts an **async provider**, so target + lists can change at runtime (e.g. followed fediverse communities); + `q=config` / `q=syndicate-to` await it. + - New `fediverse.ts` adapter: `entryToFediversePost` maps an `h-entry` onto + the `POST /publish` wire shape (`photo`/`video`/`audio` → typed + attachments with alt text, `name`+`content` → `article`, plain `content` → + `note`, community target → titled `page` + `audience`), and `syndicateEntry` + delivers to `@dwk/activitypub`'s publish endpoint when `mp-syndicate-to` + names the reserved `fediverse` uid or an advertised community. Failures + are logged per target, never fatal to the post creation. No + `@dwk/activitypub` import — the JSON wire format is the contract. + + `@dwk/activitypub` (#278/#279): + + - `createCommunitySyndicationTargets` — an async `{uid, name}` provider of + accepted `Group` follows (display handles like `!birding@lemmy.ml`), + pluggable straight into micropub's `syndicateTo`; backed by a new + internal-only `__following` DO route. + - MCP tools (v3): `activitypub_publish` (write-scoped, `PostInput` in, + handle-shaped audiences resolved via the SSRF-guarded WebFinger lookup) + and the read-only `activitypub_resolve` (handle → actor IRI + type + + profile basics), beside the existing `activitypub_list_inbox`. + - New `discovery.ts` shared by the front door and the tools: guarded handle + resolution and actor-document dereferencing. + +- 04e16c2: Add `createMicropubMcpTools` (#240): a `@dwk/mcp` tool contribution exposing + `micropub_publish` — publish a new mf2 post (`h-entry`, `h-event`, or any + other type) through the same `publishPost` path (now exported from + `handler.ts`) the HTTP `create` action uses, so both share identical + slug-generation, collision-retry, and persistence behavior. The tool is + side-effecting (`readOnlyHint: false`) and supports a `dryRun` argument that + previews the URL a publish would allocate without persisting anything. + Defaults to requiring the `create` scope, matching the HTTP endpoint. +- 8642346: Implement the proposed media-endpoint extensions (#363, roadmap #354), gated + behind `extensions.proposed`: media `q=source` (newest-first listing and + by-URL lookup, `media` scope required), the `{ "url": ... }` upload response + body, and recoverable `action=delete`/`action=undelete` via an R2 `.trash/` + prefix with scope-pair enforcement and strict URL ownership validation. + Upload metadata is now always recorded in a new `micropub_media` D1 table + (best-effort while the group is off, fail-closed when on); the new + `mediaTrashRetentionDays` config (default 30) drives trash-row pruning, with + blob purge delegated to an R2 lifecycle rule. +- 193de8d: Add opt-in proposed Audience and Location Visibility metadata, including + configured named audiences, validation, source round-tripping, and capability + advertisement. +- 2d594d1: Add the opt-in proposed Contacts (`q=contact`) extension with a private, + injectable h-card store, lifecycle actions, filtering, and pagination. +- 31f95fd: Add the proposed Location/Venue (`q=geo`) extension: a read-only proximity + search over an injected, strongly-consistent venue store, independent from + post storage. Disabled by default — requires `extensions.proposed: true` and a + configured `venues` store (`createMicropubVenueStore` for the built-in + D1-backed implementation). Accepts a Geo URI or discrete `lat`/`lon` + coordinates plus an optional `u` radius (default 1,000m, max 50,000m), and + returns venues ordered by great-circle distance with `limit`/`offset` + pagination. `geo`'s location suggestion is currently a placeholder that echoes + the query coordinates back — no reverse-geocoding service is wired in yet. + + Implements the design from #359 per + https://indieweb.org/Micropub-extensions#Location/Venue. Tracked by #354. + +- 96105d1: Add opt-in proposed filters for `q=source` post lists, including creation-time, + type, status, visibility, and exact property predicates with deterministic + keyset cursor pagination. +- 3b55292: Add `q=source` list query extension: when no `url` parameter is provided, return + `{ items: [...] }` containing the authenticated caller's recent posts, ordered + newest-first by creation time. Supports offset-based pagination via `limit` + (default 10, max 100) and `offset` (default 0) parameters. Soft-deleted posts + are excluded. Property filtering via `properties[]` applies per-item, same as + single-post `q=source` queries. + + Implements the widely-used Micropub post-list extension per + https://indieweb.org/Micropub-extensions#Query_for_Post_List. Required for + Anglesite iOS/Mac clients to browse draft posts for "resume editing" flows. + +- 99a2146: Implement the first tranche of IndieWeb Micropub extensions, toggled by maturity + group. A new `extensions` config (`{ official?, stable?, proposed? }`; defaults + `official`+`stable` on, `proposed` off) enables extensions a group at a time. + + All new extensions are **stable**: + + - **Post Status** (`post-status`) and **Visibility** (`visibility`) — their + values are validated on create and on the merged result of an update + (unrecognised values are rejected `400 invalid_request`; an absent property is + the documented default). The endpoint stores and advertises these; read-time + enforcement (hiding drafts, gating private posts) remains the serving layer's + responsibility. + - **Supported Vocabulary** — an optional `postTypes` config is advertised as + `post-types` in `q=config`. + - **Category/Tag List** (`q=category`) — returns the distinct string `category` + tags across live posts (soft-deleted excluded), alphabetised, for autocomplete; + narrowable via the stable **Limit** (`limit`) and **Filter** (`filter`) + parameters. + + Exports `validateVocabulary`, `POST_STATUS_VALUES`, `VISIBILITY_VALUES`, and the + `ExtensionMaturity`, `ExtensionGroupsConfig`, and `PostTypeConfig` types. + Post-list (`q=source` with no `url`) is tracked separately (#351/#353). + +### Patch Changes + +- 36a3be1: Bind the DPoP proof's `htu` to the configured endpoint URL instead of + `request.url` (#300). Both resource servers verified the proof against + `request.url`, but a client signs the **public** endpoint it POSTs to — behind + the path-rewriting proxy the mountable-prefix composition targets, `request.url` + is the rewritten internal URL, so every DPoP proof failed with `htu_mismatch`, a + hard outage. `authorize` now takes the expected `htu` from the caller and each + call site passes the relevant configured endpoint (`micropubEndpoint` / + `mediaEndpoint` / `microsubEndpoint`), matching what `@dwk/indieauth`'s token + endpoint already does. +- bde0341: Create D1 schema lazily so a fresh deploy no longer 500s (#291, #292). The + IndieAuth code/token store, the Micropub post store, and the Micropub/Microsub + DPoP replay stores previously created their tables only in an `init()` that no + handler ever called, so a consumer composing these packages against a brand-new + D1 hit `no such table` on the first authorization/token/publish request, and — + because DPoP replay-checking is on by default — every authenticated + create/update/delete `500`ed permanently. + + Each store now materialises its schema lazily on first use (the same + `ensureSchema` pattern the webmention/websub/microsub stores already use), with + the cached init promise cleared on failure so a transient D1 error doesn't wedge + the store. The IndieAuth RFC 8707 `resource`-column migration now runs on that + lazy path too, so it actually reaches consumer databases. No separate migration + step is required. + +- 36a3be1: Stop a client-controlled `Content-Type` on a served blob from becoming stored + XSS (#299). Both packages serve public, unauthenticated blobs whose content type + comes from the (client-controlled) upload, so an uploaded `text/html` (or + `image/svg+xml`) would render as active content on the deployment's own origin — + `@dwk/micropub`'s `media`-scope-only endpoint could thereby escalate to + origin-level script execution. The serve paths now always send + `X-Content-Type-Options: nosniff`, and only serve a known safe media type + (image/video/audio) inline; anything else is served as an opaque + `application/octet-stream` with `Content-Disposition: attachment`, so it + downloads instead of executing. (Note that `nosniff` alone would not stop an + explicit `text/html`, hence the inline allow-list.) +- bde0341: Authorize a `multipart/form-data` create before streaming its files to R2 + (#290). Previously the Micropub endpoint parsed the multipart body — including + `env.MEDIA.put(...)` for every uploaded file — before the authorization check, + so an unauthenticated caller could write arbitrary blobs to R2 (and orphan + them) simply by POSTing multipart bodies, an unauthenticated storage-exhaustion + and cost-amplification vector. The handler now parses only the text fields + up front (memory is still capped by the existing `Content-Length` guard) and + defers every file upload until after `authorize` succeeds. The dedicated media + endpoint already authorized first and is unchanged. +- Updated dependencies [36a3be1] +- Updated dependencies [3e505be] +- Updated dependencies [bde0341] +- Updated dependencies [3e505be] + - @dwk/calendar@0.1.0-beta.2 + - @dwk/indieauth@0.1.0-beta.4 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Minor Changes diff --git a/packages/micropub/package.json b/packages/micropub/package.json index 25ccca4f..d990c5c5 100644 --- a/packages/micropub/package.json +++ b/packages/micropub/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/micropub", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "Micropub create/update/delete endpoint with R2 media endpoint. Consumes IndieAuth tokens.", "keywords": [ "micropub", diff --git a/packages/microsub/CHANGELOG.md b/packages/microsub/CHANGELOG.md index 4ad0622f..a7b77246 100644 --- a/packages/microsub/CHANGELOG.md +++ b/packages/microsub/CHANGELOG.md @@ -1,5 +1,73 @@ # @dwk/microsub +## 0.1.0-beta.4 + +### Minor Changes + +- 04e16c2: Add `createMicrosubMcpTools` (#240): read-only `@dwk/mcp` tool contributions + `microsub_list_channels` (channels + unread counts) and + `microsub_get_timeline` (a page of a channel's JF2 entries, newest first), + both thin wrappers over the same `MicrosubStore` the HTTP `channels`/ + `timeline` `GET` actions use. Timeline entries originate from feeds the user + follows, so the tool description documents the prompt-injection surface — + an agent must treat entry content as untrusted data, never as instructions. + Defaults `requiredScope` to `""`, matching the HTTP `GET` actions, which + require no specific scope beyond a valid, authenticated caller. A + caller-supplied `limit` on `microsub_get_timeline` is clamped to a + configurable `maxLimit` (default 100) so an agent can't force an unbounded + D1 read. +- 39f6d61: Add a composer-injected local-dev SSRF allowlist (issue #257): `@dwk/safe-fetch` gains `allowedHosts` — exact `host[:port]` entries exempted from the private/loopback host block, with every use logged/counted as `safe_fetch.ssrf.allowed_host` — and the consuming packages expose it as `fetchAllowedHosts` in their options/config (webmention verify/discovery/send, websub verify/denial/distribute, microsub feed discovery/fetch, vc did:web resolution + status-list fetch, atproto-pds PLC directory + DID resolution). Deny-by-default is unchanged; scheme checks, redirect re-validation, timeouts, and body caps still apply to allowlisted hosts. This unblocks local `wrangler dev --local` debugging against the local dev site (Anglesite-app#708). + +### Patch Changes + +- 36a3be1: Bind the DPoP proof's `htu` to the configured endpoint URL instead of + `request.url` (#300). Both resource servers verified the proof against + `request.url`, but a client signs the **public** endpoint it POSTs to — behind + the path-rewriting proxy the mountable-prefix composition targets, `request.url` + is the rewritten internal URL, so every DPoP proof failed with `htu_mismatch`, a + hard outage. `authorize` now takes the expected `htu` from the caller and each + call site passes the relevant configured endpoint (`micropubEndpoint` / + `mediaEndpoint` / `microsubEndpoint`), matching what `@dwk/indieauth`'s token + endpoint already does. +- bde0341: Create D1 schema lazily so a fresh deploy no longer 500s (#291, #292). The + IndieAuth code/token store, the Micropub post store, and the Micropub/Microsub + DPoP replay stores previously created their tables only in an `init()` that no + handler ever called, so a consumer composing these packages against a brand-new + D1 hit `no such table` on the first authorization/token/publish request, and — + because DPoP replay-checking is on by default — every authenticated + create/update/delete `500`ed permanently. + + Each store now materialises its schema lazily on first use (the same + `ensureSchema` pattern the webmention/websub/microsub stores already use), with + the cached init promise cleared on failure so a transient D1 error doesn't wedge + the store. The IndieAuth RFC 8707 `resource`-column migration now runs on that + lazy path too, so it actually reaches consumer databases. No separate migration + step is required. + +- 36a3be1: Persist a feed's conditional-fetch validators only after its entries are stored + (#302). The poll consumer wrote the `ETag`/`Last-Modified` cache before + `insertItems`, so a transient insert failure (which retries the message) meant + the retry re-fetched with the already-updated validators, got a `304`, and + permanently dropped the entries it never stored. The cache is now written after + a successful insert (which dedups by entry id, so the re-insert on retry is + idempotent), and a `304` that omits validators keeps the previously-cached ones + instead of nulling them. +- 3e505be: Queue consumers now back off exponentially (30s base, doubling per attempt, + capped at 1h) when retrying a `message.retry()`, based on `message.attempts`. + Previously a bare `message.retry()` re-delivered at the queue's default + cadence indefinitely, hammering an unreachable source/feed/callback instead of + backing off. +- Updated dependencies [0e65ce3] +- Updated dependencies [3e505be] +- Updated dependencies [bde0341] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/indieauth@0.1.0-beta.4 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/microsub/package.json b/packages/microsub/package.json index 19b1767f..cf4dbd91 100644 --- a/packages/microsub/package.json +++ b/packages/microsub/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/microsub", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "Microsub server: channel/feed subscriptions, server-side polling, and a normalised JF2 timeline. Consumes IndieAuth tokens.", "keywords": [ "microsub", diff --git a/packages/oauth/CHANGELOG.md b/packages/oauth/CHANGELOG.md index 622cdac6..0b2bde97 100644 --- a/packages/oauth/CHANGELOG.md +++ b/packages/oauth/CHANGELOG.md @@ -1,5 +1,44 @@ # @dwk/oauth +## 0.1.0-beta.4 + +### Minor Changes + +- 7b4349c: Add the `ClientStore` seam — `getClient(clientId)` alongside `saveClient` — so + consumers building authorize/token grants over the RFC 7591 registration + handler can verify redirect URIs and client secrets through one interface. + +### Patch Changes + +- 3e505be: Dynamic client registration (`registration.ts`) no longer echoes untrusted + submitted values (`token_endpoint_auth_method`, `grant_types`, + `response_types`, `redirect_uris`) back into `error_description`, matching + `errors.ts`'s own documented rule against untrusted echoes. `readJson` now + caps the request body at 64 KiB, read incrementally, so an oversized + submission under open registration is abandoned instead of buffered in full. + + Introspection, revocation, PAR, and registration now scope their `401` + `WWW-Authenticate` challenge to the scheme the request actually attempted + (`Basic` for a `client_secret_basic` caller sending `Authorization: Basic +...`) instead of unconditionally asserting `Bearer`, which was simply wrong + for a Basic-authenticating client. + +- bde0341: Never persist client-authentication credentials into the stored PAR record + (#295). The Pushed Authorization Request handler copied every form field into + the saved `PushedRequestRecord.params`, so a client authenticating with + `client_secret_post` (or `private_key_jwt`) had its `client_secret` / + `client_assertion` written into the request store at rest. The handler now + strips `client_secret`, `client_assertion`, and `client_assertion_type` before + saving, keeping only the authorization parameters (RFC 9126). +- 36a3be1: Reject request bodies with a duplicated parameter (#308). `readForm` kept the + last occurrence of a repeated key while an `EndpointAuthenticator` reading the + cloned body sees the first (`FormData.get`), so two `client_id`s could + authenticate as one client but be attributed to another. Per RFC 6749 §3.2, + `readForm` now returns `null` on any duplicate and the introspection, revocation, + and PAR handlers reject it with `invalid_request`. +- Updated dependencies [3e505be] + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/oauth/package.json b/packages/oauth/package.json index 02bbc107..9c460fc8 100644 --- a/packages/oauth/package.json +++ b/packages/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/oauth", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "OAuth 2.0 server building blocks: RFC 8414 metadata, RFC 7662 introspection, RFC 7009 revocation, RFC 9126 PAR, RFC 7591 dynamic client registration. Cross-standard reusable; no Workers runtime dependency.", "keywords": [ "oauth2", diff --git a/packages/rdf/CHANGELOG.md b/packages/rdf/CHANGELOG.md index 6b552497..c2c0b217 100644 --- a/packages/rdf/CHANGELOG.md +++ b/packages/rdf/CHANGELOG.md @@ -1,5 +1,19 @@ # @dwk/rdf +## 0.1.0-beta.3 + +### Patch Changes + +- 3e505be: `parseJsonLd` now bounds node-value nesting depth (100 levels) during + expansion, rejecting a document nested deeper than that with a `JsonLdError`. + Previously a deeply nested `@dwk/rdf` input (mutually recursive + `processNode`/`valueToObject`) could overflow the call stack with an + uncatchable `RangeError`, escaping the package's `JsonLdError` contract. The + cap also now covers nested `@list` values (`buildList`/`valueToObject`'s own + mutual recursion for `{"@list": [{"@list": [...]}]}`-shaped input), which the + initial fix missed — a deeply `@list`-nested document could still overflow + the stack. + ## 0.1.0-beta.2 ### Patch Changes diff --git a/packages/rdf/package.json b/packages/rdf/package.json index 8419c5e4..4a275281 100644 --- a/packages/rdf/package.json +++ b/packages/rdf/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/rdf", - "version": "0.1.0-beta.2", + "version": "0.1.0-beta.3", "description": "Thin Turtle/JSON-LD parse and serialize layer over N3.js. Cross-standard reusable.", "keywords": [ "rdf", diff --git a/packages/remotestorage/CHANGELOG.md b/packages/remotestorage/CHANGELOG.md index 2be6858f..f3af13d4 100644 --- a/packages/remotestorage/CHANGELOG.md +++ b/packages/remotestorage/CHANGELOG.md @@ -1,5 +1,63 @@ # @dwk/remotestorage +## 0.1.0-beta.4 + +### Patch Changes + +- 36a3be1: Negative-cache a failed JWKS fetch so a token burst can't hammer the issuer + (#304). On a JWKS fetch failure (non-ok, malformed body, or thrown) with no + cached keys, `resolveJwks` returned without recording the failure, so every + presented-token request re-fetched the JWKS URI — an amplification/DoS vector + against the issuer's endpoint while it is down. A failed fetch is now recorded + with a short backoff (30s): within the window the last good keys are served if + available (else the request is rejected), but the issuer is not re-hit on every + request. +- 3e505be: `@dwk/solid-pod`: dropped `readReplayWindowSeconds` from `SolidPodConfig` — + it was plumbed through to `ResolvedConfig` but never consulted anywhere (no + read-side DPoP replay-window check was ever wired to it), so the config + surface promised behavior nothing implemented. `listChildren`'s WebDAV + backend now defensively drops a child IRI that isn't actually same-origin + (relevant if a forged `ldp:contains` triple, see #337, ever reaches the quad + store) instead of slicing it into a bogus, non-`/`-rooted path — the + same-origin check requires an exact match or a `/` immediately following the + origin, not just a shared string prefix (`https://example.com.attacker.com/x` + also starts with `https://example.com`'s characters, so a plain `startsWith` + check was spoofable by a suffixed host). + + `@dwk/solid-pod` and `@dwk/remotestorage`: documented the existing + `#getStore` per-isolate caching assumption (`maxInlineBytes` is taken from + whichever request builds the store first, for the DO's lifetime) — no + behavior change. + +- 9c3f652: Close two TOCTOU windows where a containment/conflict invariant was checked + outside the write transaction (#303). Because the Durable Object interleaves at + `await` points (streaming bodies), a concurrent write between the read and the + write could corrupt the invariant. + + - `@dwk/store` gains a `preserveWhere` write option: quads matching the predicate + (e.g. a container's server-managed `ldp:contains`) are re-read **inside** the + write transaction and merged into the new quad set, so a replacing write can't + clobber a membership triple a concurrent child write committed since the caller + built its quad list. + - `@dwk/solid-pod` uses it for RDF `PUT` to an existing container instead of + reading `ldp:contains` outside the `putResource` transaction, so a concurrent + child `POST` no longer has its membership triple silently dropped by a stale + snapshot. + - `@dwk/remotestorage` re-runs its document↔folder collision check inside the + write transaction via the store `guard` (a `409` now rolls the write back + atomically), so two racing PUTs to related paths can't both commit into the + document-shadows-folder collision draft §6 forbids. The pre-write check is + kept as a cheap early reject. + +- Updated dependencies [0e65ce3] +- Updated dependencies [96cc2d3] +- Updated dependencies [3e505be] +- Updated dependencies [9c3f652] +- Updated dependencies [e6fee8e] + - @dwk/webfinger@0.1.0-beta.4 + - @dwk/log@0.1.0-beta.4 + - @dwk/store@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/remotestorage/package.json b/packages/remotestorage/package.json index 0cec3ef2..c764dbfb 100644 --- a/packages/remotestorage/package.json +++ b/packages/remotestorage/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/remotestorage", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "remoteStorage (draft-dejong-remotestorage) personal data vault: OAuth-bearer GET/PUT/DELETE documents and folder listings over @dwk/store. Ships the per-account Durable Object.", "keywords": [ "remotestorage", diff --git a/packages/safe-fetch/CHANGELOG.md b/packages/safe-fetch/CHANGELOG.md index 972053f4..a9bda00d 100644 --- a/packages/safe-fetch/CHANGELOG.md +++ b/packages/safe-fetch/CHANGELOG.md @@ -1,5 +1,46 @@ # @dwk/safe-fetch +## 0.1.0-beta.3 + +### Minor Changes + +- 39f6d61: Add a composer-injected local-dev SSRF allowlist (issue #257): `@dwk/safe-fetch` gains `allowedHosts` — exact `host[:port]` entries exempted from the private/loopback host block, with every use logged/counted as `safe_fetch.ssrf.allowed_host` — and the consuming packages expose it as `fetchAllowedHosts` in their options/config (webmention verify/discovery/send, websub verify/denial/distribute, microsub feed discovery/fetch, vc did:web resolution + status-list fetch, atproto-pds PLC directory + DID resolution). Deny-by-default is unchanged; scheme checks, redirect re-validation, timeouts, and body caps still apply to allowlisted hosts. This unblocks local `wrangler dev --local` debugging against the local dev site (Anglesite-app#708). + +### Patch Changes + +- 0e65ce3: Cap the number of batches scanned per client-list page — both the outbox + owner-post merge into a Mastodon timeline and the inbox notifications scan — + so a like/announce-dominated outbox or a plain-post-dominated inbox can no + longer force a near-full-table scan per request. Also de-duplicate the + cancellable timeout-signal helper: `@dwk/safe-fetch` now exports + `createTimeoutSignal`, reused by `@dwk/activitypub` and `@dwk/webfinger` + instead of each carrying its own copy. +- 36a3be1: Block 6to4 and Teredo IPv6 addresses that embed a private IPv4 (#298). + `isPrivateOrReservedHost` already rejected IPv4-mapped / NAT64 forms, but not + `2002::/16` (6to4 — the embedded IPv4 is groups 1–2, e.g. `2002:7f00:1::` carries + `127.0.0.1`) or `2001:0000::/32` (Teredo — the client IPv4 is groups 6–7, + bitwise-inverted). Both are now decoded and rejected when the embedded IPv4 is + private/reserved, closing an SSRF bypass through those transition formats while + still allowing 6to4/Teredo addresses that wrap a public IPv4. +- 3e505be: `safeFetchJson` now rejects a response whose `Content-Type` clearly isn't + JSON (e.g. `text/html`) instead of handing it to `JSON.parse`, matching the + "content-type-checked" contract documented in `spec/packages/safe-fetch.md`. + A missing header, `text/plain`, and `+json` suffixes (`application/did+json`, + `application/ld+json`, ...) are still accepted. + + `safeFetch` now downgrades a `303` (or a `301`/`302` responding to a `POST`) + to a bodyless `GET` on the next redirect hop, matching the WHATWG Fetch + redirect algorithm and every browser's behavior, instead of re-sending the + original method and body. `Content-Type`/`Content-Encoding`/`Content-Language`/ + `Content-Location` are stripped on the downgraded hop (per the Fetch spec), + and `Content-Length` is stripped too so a stale value never survives onto a + request that no longer has a body. `307`/`308` still preserve the method and + body, as documented, and the redirect docs now note that a streamed + (`ReadableStream`) body can't be re-sent across hops. + +- Updated dependencies [3e505be] + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.2 ### Minor Changes diff --git a/packages/safe-fetch/package.json b/packages/safe-fetch/package.json index 369db109..21af244f 100644 --- a/packages/safe-fetch/package.json +++ b/packages/safe-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/safe-fetch", - "version": "0.1.0-beta.2", + "version": "0.1.0-beta.3", "description": "SSRF-safe outbound fetch and capped body reads. Cross-standard reusable; no Workers runtime dependency.", "keywords": [ "ssrf", diff --git a/packages/server/CHANGELOG.md b/packages/server/CHANGELOG.md index d5b838d0..7da57088 100644 --- a/packages/server/CHANGELOG.md +++ b/packages/server/CHANGELOG.md @@ -1,5 +1,20 @@ # @dwk/server +## 0.1.0-beta.4 + +### Patch Changes + +- 3e505be: Added a baseline security-header layer (`helmet`, with CSP left off since + `publicDir` can serve an arbitrary self-hosted site) applied to every + response, and an explicit `dotfiles: "deny"` policy on `express.static` so a + dotfile (`.env`, `.git/…`) under `publicDir` is never served regardless of a + composition's fallback route. `crossOriginResourcePolicy` is relaxed from + helmet's `same-origin` default to `cross-origin`, since this host composes + `@dwk` protocols (WebFinger, ActivityPub, IndieAuth) whose discovery documents + are explicitly meant to be fetched cross-origin by a browser. +- Updated dependencies [3e505be] + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/server/package.json b/packages/server/package.json index 13990477..323971d5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/server", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "private": true, "description": "Node.js/Express self-hosting host for the @dwk packages: Express⇄Fetch adapter, static hosting, and Node-backed shims (SQLite/filesystem) for the Cloudflare binding interfaces.", "keywords": [ diff --git a/packages/solid-pod/CHANGELOG.md b/packages/solid-pod/CHANGELOG.md index 6e8a04d4..7d44c28f 100644 --- a/packages/solid-pod/CHANGELOG.md +++ b/packages/solid-pod/CHANGELOG.md @@ -1,5 +1,126 @@ # @dwk/solid-pod +## 0.1.0-beta.4 + +### Minor Changes + +- 3a60a5c: Add `createSolidPodMcpTools` (#262): a `@dwk/mcp` tool contribution exposing + `solid_pod_read` and `solid_pod_write`. Both dispatch through the same + internal `Request` shape `createSolidPod`'s HTTP door sends to the per-pod + `SolidPodObject` Durable Object, so the pod's existing WAC evaluation is a + second, resource-level gate beneath the MCP scope check — a caller's WebID + (the MCP token's resolved `subject`) still has to be granted access under + the pod's `.acl`s. `solid_pod_write` supports a `dryRun` preview and refuses + outright when the caller has no resolved subject, since the pod's write + path requires proof of an authenticated identity. `forwardedConfig` is now + exported from `handler.ts`, and `resolveConfig`/`ResolvedConfig` from + `index.ts`, so the tool factory can build the same wire format without + duplicating it. `solid_pod_read` rejects a protocol-relative path (a + leading `//`, which `new URL` would otherwise resolve off-origin) and caps + the response body via `@dwk/safe-fetch`'s `readBodyCapped` (2 MB) so a + large pod resource can't be read into unbounded Worker memory through an + LLM-bound tool call. + +### Patch Changes + +- 36a3be1: Negative-cache a failed JWKS fetch so a token burst can't hammer the issuer + (#304). On a JWKS fetch failure (non-ok, malformed body, or thrown) with no + cached keys, `resolveJwks` returned without recording the failure, so every + presented-token request re-fetched the JWKS URI — an amplification/DoS vector + against the issuer's endpoint while it is down. A failed fetch is now recorded + with a short backoff (30s): within the window the last good keys are served if + available (else the request is rejected), but the issuer is not re-hit on every + request. +- 3e505be: `@dwk/solid-pod`: dropped `readReplayWindowSeconds` from `SolidPodConfig` — + it was plumbed through to `ResolvedConfig` but never consulted anywhere (no + read-side DPoP replay-window check was ever wired to it), so the config + surface promised behavior nothing implemented. `listChildren`'s WebDAV + backend now defensively drops a child IRI that isn't actually same-origin + (relevant if a forged `ldp:contains` triple, see #337, ever reaches the quad + store) instead of slicing it into a bogus, non-`/`-rooted path — the + same-origin check requires an exact match or a `/` immediately following the + origin, not just a shared string prefix (`https://example.com.attacker.com/x` + also starts with `https://example.com`'s characters, so a plain `startsWith` + check was spoofable by a suffixed host). + + `@dwk/solid-pod` and `@dwk/remotestorage`: documented the existing + `#getStore` per-isolate caching assumption (`maxInlineBytes` is taken from + whichever request builds the store first, for the DO's lifetime) — no + behavior change. + +- 52c3f4f: Fix a container `PUT` persisting a client-forged `ldp:contains` triple. A + container's containment listing is entirely server-managed — clients never + legitimately send it — but `#putRdf` wrote every parsed body quad verbatim, so + a Turtle/JSON-LD container `PUT` that included a forged `ldp:contains` triple + had it persisted alongside the genuine, atomically-preserved containment. A + forged triple pointing at a resource that exists (or is later created) + elsewhere would then surface as a phantom membership in the container + listing. The container branch of `#putRdf` now strips any `ldp:contains` + quad whose subject is the container IRI from the client-supplied quads before + writing, since real containment is already preserved via `preserveWhere`. +- 9c3f652: Close two TOCTOU windows where a containment/conflict invariant was checked + outside the write transaction (#303). Because the Durable Object interleaves at + `await` points (streaming bodies), a concurrent write between the read and the + write could corrupt the invariant. + + - `@dwk/store` gains a `preserveWhere` write option: quads matching the predicate + (e.g. a container's server-managed `ldp:contains`) are re-read **inside** the + write transaction and merged into the new quad set, so a replacing write can't + clobber a membership triple a concurrent child write committed since the caller + built its quad list. + - `@dwk/solid-pod` uses it for RDF `PUT` to an existing container instead of + reading `ldp:contains` outside the `putResource` transaction, so a concurrent + child `POST` no longer has its membership triple silently dropped by a stale + snapshot. + - `@dwk/remotestorage` re-runs its document↔folder collision check inside the + write transaction via the store `guard` (a `409` now rolls the write back + atomically), so two racing PUTs to related paths can't both commit into the + document-shadows-folder collision draft §6 forbids. The pre-write check is + kept as a cheap early reject. + +- 3e505be: `evaluateAccess`'s second parameter is now a single `AclResource` (the + effective ACL) instead of an `AclResource[]` chain of which only the first + entry was ever consulted — the array shape implied a multi-entry walk that + never happened. Callers passing `[acl]` now pass `acl` directly. + + Also documents (with a regression test) that a subject granting + `acl:mode`/`acl:agent`/etc. without an explicit `rdf:type acl:Authorization` + triple is not treated as an authorization — a conscious, fail-closed choice, + not an oversight. + +- 3e505be: `WEBDAV_PEPPER` is now actually mixed into the app-password hash (previously + declared as a binding but never read, so it did nothing). `@dwk/solid-pod` + now forwards it from its own `Env` into the `CredentialStore` it builds. + + Also fixed: a MKCOL request body sent without a `Content-Length` header + (chunked transfer-encoding, whose length is unknown up front) previously + defaulted to "length 0" and slipped past the RFC 4918 §9.3 unsupported-media + check. The fix now reads (and discards) the first chunk of the body to check + for actual bytes rather than inferring emptiness from headers alone, so a + legitimate empty chunked-encoded MKCOL (a non-null body stream that simply + yields no bytes) is no longer rejected alongside a real one. + +- Updated dependencies [0e65ce3] +- Updated dependencies [36a3be1] +- Updated dependencies [3e505be] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] +- Updated dependencies [9c3f652] +- Updated dependencies [e6fee8e] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/calendar@0.1.0-beta.2 + - @dwk/log@0.1.0-beta.4 + - @dwk/rdf@0.1.0-beta.3 + - @dwk/store@0.1.0-beta.4 + - @dwk/wac@0.1.0-beta.3 + - @dwk/webdav@0.1.0-beta.1 + - @dwk/ldn@0.1.0-beta.3 + ## 0.1.0-beta.3 ### Minor Changes diff --git a/packages/solid-pod/package.json b/packages/solid-pod/package.json index 4c4bd4ce..2b9bbf2d 100644 --- a/packages/solid-pod/package.json +++ b/packages/solid-pod/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/solid-pod", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "Edge-native Solid Pod: LDP verbs, content negotiation, N3 Patch, WAC, notifications. Ships the per-pod Durable Object.", "keywords": [ "solid", diff --git a/packages/store/CHANGELOG.md b/packages/store/CHANGELOG.md index eb872cca..42aae161 100644 --- a/packages/store/CHANGELOG.md +++ b/packages/store/CHANGELOG.md @@ -1,5 +1,40 @@ # @dwk/store +## 0.1.0-beta.4 + +### Minor Changes + +- 9c3f652: Close two TOCTOU windows where a containment/conflict invariant was checked + outside the write transaction (#303). Because the Durable Object interleaves at + `await` points (streaming bodies), a concurrent write between the read and the + write could corrupt the invariant. + + - `@dwk/store` gains a `preserveWhere` write option: quads matching the predicate + (e.g. a container's server-managed `ldp:contains`) are re-read **inside** the + write transaction and merged into the new quad set, so a replacing write can't + clobber a membership triple a concurrent child write committed since the caller + built its quad list. + - `@dwk/solid-pod` uses it for RDF `PUT` to an existing container instead of + reading `ldp:contains` outside the `putResource` transaction, so a concurrent + child `POST` no longer has its membership triple silently dropped by a stale + snapshot. + - `@dwk/remotestorage` re-runs its document↔folder collision check inside the + write transaction via the store `guard` (a `409` now rolls the write back + atomically), so two racing PUTs to related paths can't both commit into the + document-shadows-folder collision draft §6 forbids. The pre-write check is + kept as a cheap early reject. + +### Patch Changes + +- e6fee8e: Streamed blob writes now enroll their transient staging key in the same + transactional orphan outbox ordinary resource deletes use, from the moment the + key is created. Previously an isolate evicted mid-write (OOM/wall-clock) could + leak the staging object in R2 forever, since cleanup only ran in a `finally` + block; a leaked staging key is now forwarded and reclaimed by the existing cron + GC after its safety window, same as any other orphan. +- Updated dependencies [3e505be] + - @dwk/rdf@0.1.0-beta.3 + ## 0.1.0-beta.3 ### Minor Changes diff --git a/packages/store/package.json b/packages/store/package.json index b5c6d592..001050cd 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/store", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "DO-SQLite quad store + R2 copy-on-write blob bodies behind one storage interface.", "keywords": [ "storage", diff --git a/packages/vc/CHANGELOG.md b/packages/vc/CHANGELOG.md index f3ade9ac..7306c344 100644 --- a/packages/vc/CHANGELOG.md +++ b/packages/vc/CHANGELOG.md @@ -1,5 +1,49 @@ # @dwk/vc +## 0.1.0-beta.4 + +### Minor Changes + +- 39f6d61: Add a composer-injected local-dev SSRF allowlist (issue #257): `@dwk/safe-fetch` gains `allowedHosts` — exact `host[:port]` entries exempted from the private/loopback host block, with every use logged/counted as `safe_fetch.ssrf.allowed_host` — and the consuming packages expose it as `fetchAllowedHosts` in their options/config (webmention verify/discovery/send, websub verify/denial/distribute, microsub feed discovery/fetch, vc did:web resolution + status-list fetch, atproto-pds PLC directory + DID resolution). Deny-by-default is unchanged; scheme checks, redirect re-validation, timeouts, and body caps still apply to allowlisted hosts. This unblocks local `wrangler dev --local` debugging against the local dev site (Anglesite-app#708). + +### Patch Changes + +- bde0341: Close two critical identity-binding gaps found in the pre-1.0 code review, both + on unauthenticated / attacker-controlled paths: + + - **`@dwk/activitypub`: actor impersonation via the default key resolver + (#287, #288).** Inbound HTTP-signature verification trusted the `owner` + field of whatever document the attacker-supplied `keyId` served, so a key + document hosted at `https://evil.example/key` could declare + `owner: https://victim.example/users/alice` and have signed activities + attributed to the victim. The default resolver now binds the resolved + `owner` to the origin that **actually served** the key — the final URL after + any redirects, not the requested `keyId`, so an open redirect on the + requested origin cannot smuggle attacker-served content in under it — and the + `keyId` fetch runs through `@dwk/safe-fetch`'s + `safeFetch` — `https:`-only, with private/loopback/link-local hosts blocked + and **every redirect hop re-validated** (so a public host cannot `302` the + fetch onto an internal address), plaintext `http:` no longer accepted, and a + bounded, size-capped body read — instead of an unguarded `fetch`. + + - **`@dwk/vc`: credential forgery via unbound `verificationMethod` (#289).** + Proof verification never tied the proof's `verificationMethod` to the + credential's `issuer`, so a credential naming any `issuer` could be signed + with an attacker's own key and still verify. `verifySingleProof` now + requires the verification method's controller (its declared `controller`, + or the DID/URL portion of the method id) to equal the credential's issuer + before the key is trusted. A new optional `expectedController` on + `VerifyProofOptions` allows overriding the bound party for non-issuance + proof purposes (e.g. a presentation's `authentication` proof). + +- Updated dependencies [0e65ce3] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Minor Changes diff --git a/packages/vc/package.json b/packages/vc/package.json index 3682271a..545a34eb 100644 --- a/packages/vc/package.json +++ b/packages/vc/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/vc", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "did:web identity plus Verifiable Credential (VCDM 2.0) issuance, verification, and Bitstring Status List revocation.", "keywords": [ "verifiable-credentials", diff --git a/packages/wac/CHANGELOG.md b/packages/wac/CHANGELOG.md index 178a7df0..cf3c2f70 100644 --- a/packages/wac/CHANGELOG.md +++ b/packages/wac/CHANGELOG.md @@ -1,5 +1,22 @@ # @dwk/wac +## 0.1.0-beta.3 + +### Patch Changes + +- 3e505be: `evaluateAccess`'s second parameter is now a single `AclResource` (the + effective ACL) instead of an `AclResource[]` chain of which only the first + entry was ever consulted — the array shape implied a multi-entry walk that + never happened. Callers passing `[acl]` now pass `acl` directly. + + Also documents (with a regression test) that a subject granting + `acl:mode`/`acl:agent`/etc. without an explicit `rdf:type acl:Authorization` + triple is not treated as an authorization — a conscious, fail-closed choice, + not an oversight. + +- Updated dependencies [3e505be] + - @dwk/rdf@0.1.0-beta.3 + ## 0.1.0-beta.2 ### Patch Changes diff --git a/packages/wac/package.json b/packages/wac/package.json index 6093e78c..5e483372 100644 --- a/packages/wac/package.json +++ b/packages/wac/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/wac", - "version": "0.1.0-beta.2", + "version": "0.1.0-beta.3", "description": "Web Access Control evaluation (effective-ACL walk, Append vs Write). Solid-specific helper.", "keywords": [ "web-access-control", diff --git a/packages/webauthn/CHANGELOG.md b/packages/webauthn/CHANGELOG.md index f0fd2386..cb4cf973 100644 --- a/packages/webauthn/CHANGELOG.md +++ b/packages/webauthn/CHANGELOG.md @@ -1,5 +1,32 @@ # @dwk/webauthn +## 0.1.0-beta.4 + +### Patch Changes + +- bde0341: Add a per-operation `authorize` hook so registration can be gated (#293). + `register/options` and `register/verify` bind a passkey to a caller-supplied + `user.id`; with no way to require authentication, a composition that mounted the + handler unauthenticated let anyone register their own authenticator against + another user's id and then authenticate as that user — account takeover. + + `WebAuthnConfig` now accepts `authorize(operation, request) => boolean`, + consulted by the front door before any Durable Object state is touched; a + `false` result returns `401`. The default is allow-all (matching `@dwk/vc`), so + existing behaviour is unchanged, but the field doc and README now loudly direct + the composing front door to gate the `register/*` operations behind an + authenticated session. The hook is not forwarded to the DO. + + When no `authorize` hook is supplied, `createWebAuthn` now emits a loud + `webauthn.config.registration_unguarded` warning on the injected logger at + startup rather than degrading silently, so an accidentally-open registration + surface is visible in logs (composition-contract "no silent degradation" + posture). It stays advisory — upstream gating at the front door is a valid + pattern the package cannot observe. + +- Updated dependencies [3e505be] + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/webauthn/package.json b/packages/webauthn/package.json index dce23aae..468123c2 100644 --- a/packages/webauthn/package.json +++ b/packages/webauthn/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/webauthn", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "WebAuthn / passkeys relying party: registration + authentication ceremonies over a per-RP Durable Object for challenge state and credential records.", "keywords": [ "webauthn", diff --git a/packages/webdav/CHANGELOG.md b/packages/webdav/CHANGELOG.md index e5ae0c49..90efd07e 100644 --- a/packages/webdav/CHANGELOG.md +++ b/packages/webdav/CHANGELOG.md @@ -1,5 +1,27 @@ # @dwk/webdav +## 0.1.0-beta.1 + +### Patch Changes + +- 36a3be1: Anchor the app-password `pathPrefix` scope on a path-segment boundary (#309). + The check used a raw `startsWith`, so a credential scoped to `/photos` also + authorized the sibling `/photos-private`. It now matches only the prefix + collection itself or a true descendant (`path === base || path.startsWith(base + +"/")`), so a scoped credential can no longer reach adjacent same-prefix + collections. (WAC still applies as the second gate.) +- 3e505be: `WEBDAV_PEPPER` is now actually mixed into the app-password hash (previously + declared as a binding but never read, so it did nothing). `@dwk/solid-pod` + now forwards it from its own `Env` into the `CredentialStore` it builds. + + Also fixed: a MKCOL request body sent without a `Content-Length` header + (chunked transfer-encoding, whose length is unknown up front) previously + defaulted to "length 0" and slipped past the RFC 4918 §9.3 unsupported-media + check. The fix now reads (and discards) the first chunk of the body to check + for actual bytes rather than inferring emptiness from headers alone, so a + legitimate empty chunked-encoded MKCOL (a non-null body stream that simply + yields no bytes) is no longer rejected alongside a real one. + ## 0.1.0-beta.0 ### Minor Changes diff --git a/packages/webdav/package.json b/packages/webdav/package.json index c10bf82c..a21617fd 100644 --- a/packages/webdav/package.json +++ b/packages/webdav/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/webdav", - "version": "0.1.0-beta.0", + "version": "0.1.0-beta.1", "description": "WebDAV (RFC 4918, Class 2) façade over a Solid pod: mount your own storage as a native network drive. Protocol core — bounded XXE-safe XML, app-password auth, If-header parsing.", "keywords": [ "webdav", diff --git a/packages/webfinger/CHANGELOG.md b/packages/webfinger/CHANGELOG.md index 7340c294..c2ad1323 100644 --- a/packages/webfinger/CHANGELOG.md +++ b/packages/webfinger/CHANGELOG.md @@ -1,5 +1,60 @@ # @dwk/webfinger +## 0.1.0-beta.4 + +### Minor Changes + +- 96cc2d3: FEP-1b12 group participation (fediverse interop phase 2, #275) and the + WebFinger client half (#277). + + `@dwk/webfinger` gains `lookup.ts` — the pure client half of RFC 7033: + `parseHandle` (bare / `@user@host` / `!community@host` / `acct:` forms), + `webfingerQueryUrl`, `selectActorLink`, and `resolveHandle` with an injected + `fetch` (the package still makes no network calls of its own). + + `@dwk/activitypub` participates in FEP-1b12 communities: + + - **Follow-target typing (§2.1):** `following` rows record `actor_type`, + `inbox`, and `shared_inbox`, resolved from the actor document off the + critical path; pre-existing rows are lazily backfilled by the alarm tick + (permanent failures mark `Unknown`), so old Group follows qualify without + re-following. Owner-published `Follow` / `Undo(Follow)` now record the + relationship and deliver to the target actor instead of fanning out. + - **Announce unwrapping (§2.2):** an `Announce` from a followed `Group` + wrapping a member activity stores the inner activity attributed to its real + author — deduped by inner id, tagged `relayed_by` + group `audience`. + - **Async two-tier origin verification, on by default (§2.2):** relayed + content (`Create`/`Update`/`Delete`) verifies against its origin on the + next alarm tick; votes (`Like`/`Dislike`) verify in batched sweeps. + `verify_state` tracks `pending → verified`; a refuted row is deleted. + Config: `verifyRelayedObjects: "tiered" (default) | "immediate" | "off"`. + - **Community posting (§2.3):** a shaped post with an `audience` Group is + additionally delivered to the group's inbox (resolved from the alarm when + unknown); the group announces it to members per FEP-1b12. + - **Community discovery (§2.4):** a handle-shaped `audience` + (`!birding@lemmy.ml`) on `POST /publish` resolves to its Group actor + IRI at the stateless front door via the `@dwk/webfinger` helper behind the + SSRF guard. + - **`Dislike`** accepted inbound (stored like `Like`) and publishable + outbound (with `Undo`). + +### Patch Changes + +- 0e65ce3: Cap the number of batches scanned per client-list page — both the outbox + owner-post merge into a Mastodon timeline and the inbox notifications scan — + so a like/announce-dominated outbox or a plain-post-dominated inbox can no + longer force a near-full-table scan per request. Also de-duplicate the + cancellable timeout-signal helper: `@dwk/safe-fetch` now exports + `createTimeoutSignal`, reused by `@dwk/activitypub` and `@dwk/webfinger` + instead of each carrying its own copy. +- Updated dependencies [0e65ce3] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/webfinger/package.json b/packages/webfinger/package.json index 679b1e76..709f9304 100644 --- a/packages/webfinger/package.json +++ b/packages/webfinger/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/webfinger", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "WebFinger (RFC 7033) account/resource discovery endpoint at /.well-known/webfinger.", "keywords": [ "webfinger", diff --git a/packages/webmention/CHANGELOG.md b/packages/webmention/CHANGELOG.md index d67757f1..d440ec2f 100644 --- a/packages/webmention/CHANGELOG.md +++ b/packages/webmention/CHANGELOG.md @@ -1,5 +1,35 @@ # @dwk/webmention +## 0.1.0-beta.4 + +### Minor Changes + +- 04e16c2: Add `createWebmentionMcpTools` (#240): a read-only `@dwk/mcp` tool + contribution `webmention_list_received`, listing verified Webmentions newest + first (optionally scoped to a `target` URL) over the same `InboxStore` the + queue consumer writes into. This is a new capability with no existing HTTP + `GET` endpoint to mirror (the receiver only ever accepts `POST`), so it + defaults `requiredScope` to `"read"` rather than an open scope. Mentions + originate from third-party pages, so the tool description documents the + prompt-injection surface — an agent must treat mention content as untrusted + data, never as instructions. +- 39f6d61: Add a composer-injected local-dev SSRF allowlist (issue #257): `@dwk/safe-fetch` gains `allowedHosts` — exact `host[:port]` entries exempted from the private/loopback host block, with every use logged/counted as `safe_fetch.ssrf.allowed_host` — and the consuming packages expose it as `fetchAllowedHosts` in their options/config (webmention verify/discovery/send, websub verify/denial/distribute, microsub feed discovery/fetch, vc did:web resolution + status-list fetch, atproto-pds PLC directory + DID resolution). Deny-by-default is unchanged; scheme checks, redirect re-validation, timeouts, and body caps still apply to allowlisted hosts. This unblocks local `wrangler dev --local` debugging against the local dev site (Anglesite-app#708). + +### Patch Changes + +- 3e505be: Queue consumers now back off exponentially (30s base, doubling per attempt, + capped at 1h) when retrying a `message.retry()`, based on `message.attempts`. + Previously a bare `message.retry()` re-delivered at the queue's default + cadence indefinitely, hammering an unreachable source/feed/callback instead of + backing off. +- Updated dependencies [0e65ce3] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Minor Changes diff --git a/packages/webmention/package.json b/packages/webmention/package.json index 43eeb12a..4467a1df 100644 --- a/packages/webmention/package.json +++ b/packages/webmention/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/webmention", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "Webmention receiver (async verification queue) and sender.", "keywords": [ "webmention", diff --git a/packages/websub/CHANGELOG.md b/packages/websub/CHANGELOG.md index b1156704..0858cb58 100644 --- a/packages/websub/CHANGELOG.md +++ b/packages/websub/CHANGELOG.md @@ -1,5 +1,46 @@ # @dwk/websub +## 0.1.0-beta.4 + +### Minor Changes + +- 39f6d61: Add a composer-injected local-dev SSRF allowlist (issue #257): `@dwk/safe-fetch` gains `allowedHosts` — exact `host[:port]` entries exempted from the private/loopback host block, with every use logged/counted as `safe_fetch.ssrf.allowed_host` — and the consuming packages expose it as `fetchAllowedHosts` in their options/config (webmention verify/discovery/send, websub verify/denial/distribute, microsub feed discovery/fetch, vc did:web resolution + status-list fetch, atproto-pds PLC directory + DID resolution). Deny-by-default is unchanged; scheme checks, redirect re-validation, timeouts, and body caps still apply to allowlisted hosts. This unblocks local `wrangler dev --local` debugging against the local dev site (Anglesite-app#708). +- 2de1ea6: Distribute content per-subscriber instead of in one unbounded fan-out. A + publish now enqueues a `distribute` job that fetches the topic snapshot once and + plans the fan-out: it enqueues one `deliver` job per active subscriber, each of + which POSTs to a single callback and retries on its own queue checkpoint. This + fixes three problems with the previous once-only `Promise.all` fan-out: a + subscriber down during a publish no longer silently misses the update; a large + subscriber set no longer exceeds a single invocation's subrequest ceiling; and a + transient failure no longer re-delivers to every subscriber (duplicates). + + Small snapshots ride inline in each `deliver` message, **base64-encoded** (a raw + `Uint8Array` would be JSON-serialized by Cloudflare Queues into a ~10×-larger + `{"0":…}` byte map and not round-trip as bytes). A body too large to inline + (> `MAX_INLINE_BODY_BYTES`, 64 KB raw) is staged once in a new optional + `WEBSUB_CONTENT` R2 bucket and referenced by key. When a snapshot exceeds the + inline limit and no bucket is configured, the fan-out fails loudly rather than + truncating the push. Delivery is at-least-once: a `distribute` retry after a + partial `sendBatch` fan-out can re-enqueue deliver jobs for subscribers already + reached (WebSub §7 requires subscribers to tolerate duplicates). + `deliverToSubscriber` now accepts the narrower `DeliveryTarget` shape + (`Subscription` remains assignable). + +### Patch Changes + +- 3e505be: Queue consumers now back off exponentially (30s base, doubling per attempt, + capped at 1h) when retrying a `message.retry()`, based on `message.attempts`. + Previously a bare `message.retry()` re-delivered at the queue's default + cadence indefinitely, hammering an unreachable source/feed/callback instead of + backing off. +- Updated dependencies [0e65ce3] +- Updated dependencies [3e505be] +- Updated dependencies [36a3be1] +- Updated dependencies [39f6d61] +- Updated dependencies [3e505be] + - @dwk/safe-fetch@0.1.0-beta.3 + - @dwk/log@0.1.0-beta.4 + ## 0.1.0-beta.3 ### Patch Changes diff --git a/packages/websub/package.json b/packages/websub/package.json index e0ee5469..0b0d6a3a 100644 --- a/packages/websub/package.json +++ b/packages/websub/package.json @@ -1,6 +1,6 @@ { "name": "@dwk/websub", - "version": "0.1.0-beta.3", + "version": "0.1.0-beta.4", "description": "WebSub (W3C) hub: subscription store, intent verification, and signed content distribution.", "keywords": [ "websub",