sync(master): align with merged outlaw-dame/memory state#68
Conversation
Adds server.allowedHosts: true so the app works when accessed via tunnelled/proxied URLs (Manus sandbox, ngrok, Cloudflare Tunnel, etc.)
docker-compose.local.yml:
- Add Fuseki (RDF triple store), Redis, and ActivityPods backend services
- ActivityPods backend runs on host network (port 3000) so it can reach
the Memory API at localhost:8794 — matches original dev docker-compose
- PostgreSQL, Memory API, and Vite frontend remain on bridge network
- Mastopod sidecar uses host network to reach Memory API webhook
api/src/types/enums.ts:
- Replace fixed t.Enum({ 'http://localhost:3000': ... }) with t.String()
- Accepts any valid http:// or https:// URL as pod provider endpoint
- Enables connecting to external providers (activitypods.org, etc.)
frontend/src/components/SignInForm.vue:
- Show 'Pod Provider URL' field (default: http://localhost:3000)
- Add async/await + try/catch error handling
- Show inline error message on failure
frontend/src/components/SignupForm.vue:
- Show 'Pod Provider URL' field (default: http://localhost:3000)
- Clear test-only pre-filled values (username/email/password)
- Add endpoint URL validation
- Show general error message on failure
…harden stores and views Replaces boxicons with @lucide/vue for iOS/macOS/web and Material Symbols for Android, using the Font Loading API to ensure graceful fallback. Adds AppIcon.vue component, AppIcon.types.ts, and usePlatform.ts composable. Hardens authStore and postsStore ESLint `no-explicit-any` suppression to inline disable comments on the correct `}) as any` lines. Fixes v-if+v-for co-location in NotificationsView, removes unused computed in MessagesView, and drops unused drizzle-orm import from useFollow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds elysiaCompat.ts exporting signedIn / signedInGuard so route options
can spread the macro consistently. Renames the setup.ts derive helper
from error() to status() (matches Elysia 1.4's renamed handler context)
and updates all consumers to call status(N, msg). Converts route
detail: 'X' shorthand to detail: { description: 'X' } per Elysia 1.4
schema. No behavior, log message, or response-shape changes.
Adds 0015 migration introducing media_attachments (uuid pk, FK to users and posts, state/kind enums with check constraints, focus/blurhash/ dimensions/duration metadata, canonical+gateway URL, filebaseCid, digestMultibase, expiresAt, three indexes) and extends posts with clientPostKey/clientPostRequestHash plus a partial unique index for client-side idempotency. Schema-only; consumers added in later commits.
- Adds services/MediaAttachments.ts with create/find/markAttached helpers, request hashing, idempotency-key normalization, attachable-id resolution, and the MediaAttachmentError sentinel. - Adds services/MediaAttachments.test.ts covering the helpers above. - Extends services/ActivityPod with static uploadMedia(user, file, slug) posting to <endpoint>/<userName>/data/semapps/file with bearer auth and a 5-minute in-process profile cache (invalidated by updateProfile). - Adds routes/mediaUploads.ts (authenticated upload endpoint) and routes/mediaSidecarCallback.ts (sidecar finalize webhook). Plugins are not yet registered; wiring lands in the next commit.
postPayload.ts: adds MAX_MEDIA_ATTACHMENTS, MEDIA_EXTENSION_MIME_TYPES, SUPPORTED_ATTACHMENT_MEDIA_TYPES regex, extractMediaAttachments(), mergeMediaAttachments(), and an attachments field on BuildOutboxPostInput. routes/posts.ts: requires content-or-attachment per request, resolves attachment IDs into media rows, marks them attached after persistence, and threads idempotency: hashes the request, looks up an existing post via clientPostKey, returns the existing post on match and 409 on hash conflict, otherwise inserts a new row.
Registers mediaUploadsPlugin (protected) and mediaSidecarCallbackPlugin (public) on the Elysia app, exports them from routes/index.ts, adds media-related translation keys in i18n.ts, and surfaces media DTOs in types/index.ts.
…ication Applies the elysiaCompat patterns (signedInGuard, error→status helper, detail object, ...signedIn route option) across atBridge.ts. Also clarifies the /subscribe AT-source registration response: Memory stores AT source metadata for UI visibility only; firehose runtime ownership lives in fedify-sidecar. Adds a warning field and adjusts the success message and response schema accordingly.
Aligns ORM/CLI versions with the schema features used in 0015 (uuid pk + check constraints + partial unique index syntax).
…le install - docker-compose.local.yml: move the bundled fedify-sidecar service behind a 'legacy-at-harness' compose profile so the default Memory dev stack no longer spins it up. - docker/api.dockerfile: pass --ignore-scripts to bun install to skip postinstall scripts during image build for reproducibility.
- Adds frontend/public/manifest.json with name/icons/theme metadata. - Adds frontend/src/platform/capabilities.ts surfacing runtime feature detection used by the composer and media flows. - Updates frontend/index.html to link the manifest and apple-touch-icon, add theme-color and mobile-web-app meta tags, and self-close void elements.
- CreatePostForm.vue: media attachment uploader UI, idempotency-key generation, and content-or-attachment guard. - UnifiedFeedItem.vue / ThreadSummary.vue / PostPoll.vue: render media attachments (image/gif/video) with focus + alt-text + blurhash hints. - GifPicker.vue + composables/useKlipy.ts: align with composer media pipeline. - types/api.ts: media DTOs and idempotency fields on post requests. - i18n/messages.ts + i18n/index.ts: media + idempotency translation keys.
…tyles Refreshes SignInForm.vue, frontend/src/db/localDb.ts, router/index.ts, embed/pglite workers, and theme styles (style.scss, vusax-overrides.scss, welcome.scss) so they remain consistent with the media + PWA changes that landed in earlier commits.
Mirror the ActivityPods backend / Fedify sidecar Safe Browsing v5alpha1
contract on Memory's link-preview surface so the three components share
the same URL hygiene posture.
- New api/src/utils/safeBrowsing.ts with passesSafeBrowsing(url):
- Reads GOOGLE_SAFE_BROWSING_API_KEY (preferred) or SAFE_BROWSING_API_KEY.
- Disabled (returns true) when no key configured.
- 2.5s timeout via AbortSignal.timeout; fail-open by default.
- SAFE_BROWSING_FAIL_CLOSED=1 inverts behavior on transport / parse errors.
- Rejects non-http(s) urls outright.
- LinkPreviewService.fetchLinkPreview now:
- Calls passesSafeBrowsing on the user-supplied URL before issuing the
fetch, and again on the post-redirect URL when it differs.
- sanitizeHttpUrl tightened against SSRF:
* IPv6 unspecified / loopback / link-local (fe80::/10) /
ULA (fc00::/7) / multicast (ff00::/8) / IPv4-mapped (::ffff:*).
* IPv4 0.0.0.0/8, CGNAT 100.64/10, doc ranges 192.0.0.0/24,
192.0.2.0/24, 198.18.0.0/15, 192.88.99.0/24, 203.0.113.0/24,
and class D/E (224.0.0.0/3).
* Numeric / hex hostnames (decimal IP encodings such as
http://2130706433/) are rejected.
- sanitizeHttpUrl exported for unit testing and reuse.
- Route handler maps the new error categories (numeric host / safe
browsing / credentials) to HTTP 400.
- Tests: 21 new bun:test cases covering Safe Browsing fail modes,
env-var precedence, and every SSRF guard branch. Full suite: 153/153.
- .env.example documents GOOGLE_SAFE_BROWSING_API_KEY and
SAFE_BROWSING_FAIL_CLOSED.
Companion to AP backend commit 3831fb90 (Safe Browsing on opengraph
fetcher) — same env contract, same fail-open default.
Address the residual risks called out alongside the link-preview Safe
Browsing landing:
1. DNS rebinding via redirect: 'follow'.
2. ApRemoteIngestionService.cacheApActorIfStale fetched federation actor
URIs through raw fetch with no scheme / private-IP / Safe Browsing
guards.
3. extractMediaUrl stored avatar/banner URLs after only an isHttpsUrl
check, allowing federated actors to point browsers at private hosts.
Changes
-------
* api/src/utils/urlGuards.ts (new) — single source of truth for URL
hygiene primitives:
- sanitizeHttpUrl: rejects non-http(s), userinfo, IPv4 0/8, RFC1918,
127/8, 169.254/16 (incl. AWS IMDS), CGNAT 100.64/10, doc/benchmark
ranges (192.0.0/24, 192.0.2/24, 198.18/15, 192.88.99/24, 203.0.113/24),
multicast/E (224-255/4), IPv6 ::, ::1, fe80::/10, fc00::/7, ff00::/8,
::ffff: mapped, and decimal/hex IP-literal hostnames.
- assertPublicHost: async DNS lookup that validates *every* returned
A/AAAA record against the same IP blocklist, mitigating the common
DNS-rebind case where a public hostname resolves to private space.
- isPublicHttpUrl: synchronous predicate for storage paths that should
not fetch but still need to refuse private/loopback URLs.
- UrlGuardError typed code so callers can branch.
* api/src/utils/secureFetch.ts (new) — outbound HTTP wrapper:
- sanitize -> assertPublicHost (DNS) -> Safe Browsing -> fetch with
redirect: 'manual'. Each redirect Location is run through the full
pipeline before the next hop. Caps redirects at 5 by default.
- Honors caller AbortSignal and a per-call timeoutMs (default 4500ms).
- Documents the residual rebind window (Bun fetch lacks a connect-
pinning hook); flagged for follow-up when the runtime moves to Node.
* LinkPreviewService.ts: replaces inline sanitize + manual fetch with
secureFetch; old sanitizeHttpUrl re-exported as a thin wrapper for
test compatibility.
* ApRemoteIngestionService.ts: cacheApActorIfStale now uses secureFetch
(so AP actor fetches go through scheme/IP/SB checks); extractMediaUrl
validates avatar/banner URLs with isPublicHttpUrl, so a federated
actor can no longer plant http://10.0.0.1/ in the actor cache.
* routes/linkPreview.ts: maps new error categories (dns/redirect/too many)
to HTTP 400.
Tests
-----
* api/src/utils/urlGuards.test.ts (new, 5 cases) — predicate coverage.
* api/src/utils/secureFetch.test.ts (new, 11 cases) — pre-fetch guards,
manual redirect chain, redirect-target re-validation (private IPs +
non-http(s)), redirect cap, AbortSignal pass-through, Safe Browsing
per-hop.
* Existing LinkPreviewService.test.ts and safeBrowsing.test.ts still
pass against the refactored implementation.
* Full bun test: 169/169 pass. tsc --noEmit clean.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8af7e10b04
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| REDIS_HOST: redis | ||
| REDIS_PORT: 6379 | ||
| # ActivityPods uses host networking so it can reach the app backend at localhost:8794 | ||
| network_mode: host |
There was a problem hiding this comment.
Keep ActivityPods on the Compose network
In docker-compose.local.yml I checked the new activitypods service: it configures SEMAPPS_JENA_URL=http://fuseki:3030 and REDIS_HOST=redis, but network_mode: host takes the container out of the Compose network where those service-name DNS records exist. Docker's Compose reference also notes that port mapping must not be used with network_mode: host, so docker compose -f docker-compose.local.yml up will either reject this service or start it unable to resolve Fuseki/Redis; remove host mode or point these dependencies at host-reachable addresses.
Useful? React with 👍 / 👎.
| if (!endpoint.value.startsWith('http')) { | ||
| errorMessages.value.endpoint = 'Must be a valid http:// or https:// URL' |
There was a problem hiding this comment.
Reject unsupported pod providers before submitting
This validation accepts any http... URL, and the form even suggests https://activitypods.org, but the API schema for signup only accepts viablePodProviders built from localhost plus configured env endpoints (api/src/types/enums.ts). With the default stack, entering the suggested public URL passes client validation and then gets rejected by TypeBox before the route runs; authStore.signup ignores non-500 failures, so the user sees no actionable error. Validate against the same configured provider set or surface the API rejection instead of accepting arbitrary URLs here.
Useful? React with 👍 / 👎.
Summary
Sync
AmoreTechLllc/memorymasterwith the already-merged state fromoutlaw-dame/memory.This includes:
mastercommits previously unique tooutlaw-dame/memoryfeature/platform-adaptive-iconsWhy
Keeps one clean, easy-to-follow canonical history across your active forks and removes ambiguity about where current work lives.
Validation
origin/masterto sync lineage verifiedoutlaw-dame/memoryPR Implement Basisc of the api #2 already merged