Skip to content

feat: add link previews with OG embeds and background worker#14

Merged
BuckyMcYolo merged 5 commits into
mainfrom
dev
Mar 5, 2026
Merged

feat: add link previews with OG embeds and background worker#14
BuckyMcYolo merged 5 commits into
mainfrom
dev

Conversation

@BuckyMcYolo
Copy link
Copy Markdown
Owner

@BuckyMcYolo BuckyMcYolo commented Mar 5, 2026

service

Introduce apps/worker BullMQ service for async link unfurling — extracts URLs from sent messages, fetches Open Graph metadata, persists embeds to DB, and broadcasts updates via @socket.io/redis-emitter.

Add Discord-style embed cards to chat UI with full-width thumbnails, linked titles, and site name footer. Proxy X/Twitter URLs through fxtwitter.com for OG fetching.

Add @tiptap/extension-link for auto-linking pasted URLs in the composer, with ++markdown wrapper stripping for clean stored content.

Pull Request Summary

Overview

Adds asynchronous link unfurling with Open Graph embeds: new worker using BullMQ to fetch OG metadata, realtime embed broadcasting via a Redis-backed Socket.IO emitter, API/schema/type updates to surface embeds, web UI embed cards, and Composer autolinking/sanitization. X/Twitter URLs are proxied through fxtwitter.com when fetching OG metadata. Redis URL parsing preserves TLS and DB index where used.

Key Changes

  • New worker app

    • apps/worker: BullMQ Worker bootstrap (concurrency 5), Redis-backed Socket.IO Emitter, graceful shutdown, worker error/failure logging.
    • apps/worker/src/jobs/link-unfurl.ts: processor factory that validates/safely fetches OG data (open-graph-scraper with timeout and UA), applies proxy rules (X/Twitter → fxtwitter.com), constructs Embed objects, updates message.embeds in the DB, and emits "message:embeds:updated".
  • Realtime server

    • apps/realtime: enqueues LINK_UNFURL_QUEUE jobs after message creation with LinkUnfurlJobData { messageId, channelId, urls } (new Queue).
    • parseRedisUrl / Redis client usage preserves TLS and DB selection when connecting to Redis.
  • Types & contracts

    • packages/realtime-types: added LINK_UNFURL_QUEUE and LinkUnfurlJobData; new RealtimeEmbed type; RealtimeMessage now includes embeds; added "message:embeds:updated" event and exported queues module.
    • packages/db: Embed type includes optional siteName; message.embeds is a jsonb column with default [].
  • API / schemas

    • apps/api: Zod OpenAPI schemas add messageEmbedSchema (type, url, title?, description?, thumbnail?, siteName?) and include embeds: z.array(messageEmbedSchema) on messageWithAuthorSchema. fetchMessagePage / API wiring default embeds to [] when missing.
  • Web client / UI

    • New EmbedCard component with clickable title, description, full-width thumbnail support, siteName footer, and image error handling.
    • Message rendering updated to display embed cards in message-item.
    • Composer: adds @tiptap/extension-link (autolink, linkOnPaste) and sanitizes stored markdown by normalizing NBSPs and stripping TipTap ++ wrapper artifacts before storing.
    • Realtime client: listens for "message:embeds:updated" and updates cached messages (use-message-sending).
  • Packaging & build

    • New apps/worker package.json, tsup and tsconfig entries.
    • apps/web added @tiptap/extension-link; apps/realtime added bullmq; apps/worker depends on open-graph-scraper, bullmq, @socket.io/redis-emitter, redis.

Implementation notes & safety

  • URL safety: isSafeUrl performs scheme checks, rejects localhost/loopback and hostnames matching private IP patterns, and resolves hostnames via DNS lookup to filter private addresses (mitigates SSRF).
  • Proxying: OG fetcher rewrites select hosts (X/Twitter) to fxtwitter.com for OG scraping and surfaces a siteName when proxied.
  • Job robustness: BullMQ job options include removal policy; worker logs failures and exposes an error listener; DB update result checked before emit.
  • DB compatibility: packages/db message schema declares embeds as jsonb(...).default([]), avoiding a migration blocker for storing embeds.

Concerns & Recommendations

  • Operational: consider rate-limiting, caching, deduplication for OG fetches, and worker metrics to reduce external load and improve observability.
  • Retries & backoff: current worker relies on BullMQ defaults; consider explicit retry/backoff and monitoring for transient fetch failures.
  • Edge cases: URL extraction and regex handling for pasted content (punctuation, trailing chars) and broader proxy rules should be validated with real-world links.
  • Security: isSafeUrl uses DNS lookup which is good, but ensure DNS resolution behavior and lookup settings are acceptable in deployed environment (e.g., internal resolver behaviour).

Files & Areas Affected (high level)

  • apps/worker/*
  • apps/realtime/src/index.ts, services/messages.ts, package.json
  • apps/web/src/components/chat/embed-card.tsx, composer/message-input.tsx, message-item.tsx, package.json
  • apps/api/src/lib/helpers/openapi/message-schemas.ts, src/lib/queries/messages.ts
  • packages/db/src/schemas/messages.ts
  • packages/realtime-types/src/{events.ts,queues.ts}, package.json, exports

Confidence Score: 4/5

Rationale: Implementation is well-scoped and broadly integrated across worker, realtime, API, types, and web. Key safety measures (DNS-based private IP filtering, scheme checks, proxy rules) are present and message embeds are stored on a jsonb column with a default. Remaining items are operational and edge-case hardening (caching/deduping, metrics, retry policies, and URL extraction edge cases).

service

Introduce apps/worker BullMQ service for async link unfurling — extracts
URLs from sent messages, fetches Open Graph metadata, persists embeds to
DB, and broadcasts updates via @socket.io/redis-emitter.

Add Discord-style embed cards to chat UI with full-width thumbnails,
linked titles, and site name footer. Proxy X/Twitter URLs through
fxtwitter.com for OG fetching.

Add @tiptap/extension-link for auto-linking pasted URLs in the composer,
with ++markdown wrapper stripping for clean stored content.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 5, 2026

Warning

Rate limit exceeded

@BuckyMcYolo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 24 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28f8bc5b-9764-412b-8de1-ea2c73c3edfb

📥 Commits

Reviewing files that changed from the base of the PR and between d179c82 and 9f4a363.

📒 Files selected for processing (1)
  • apps/worker/src/jobs/link-unfurl.ts
📝 Walkthrough

Walkthrough

Adds link-unfurling: messages gain embeds, realtime server enqueues unfurl jobs to BullMQ, a new worker fetches Open Graph data and updates message embeds in the DB, and the frontend renders embeds and listens for realtime embed updates.

Changes

Cohort / File(s) Summary
Schemas & Types
packages/realtime-types/src/events.ts, packages/realtime-types/src/queues.ts, packages/realtime-types/src/index.ts, packages/realtime-types/package.json, apps/api/src/lib/helpers/openapi/message-schemas.ts, packages/db/src/schemas/messages.ts
Add RealtimeEmbed type and embeds: RealtimeEmbed[] on messages; add LINK_UNFURL_QUEUE and LinkUnfurlJobData; export queues from realtime-types; add httpsUrlSchema and messageEmbedSchema to OpenAPI; add optional siteName to DB embed schema.
Realtime server
apps/realtime/package.json, apps/realtime/src/index.ts, apps/realtime/src/services/messages.ts
Add bullmq dependency; initialize a BullMQ Queue for link-unfurl jobs; enqueue unfurl jobs after sending messages; ensure outgoing messages include embeds: [].
Worker app (new)
apps/worker/package.json, apps/worker/tsconfig.json, apps/worker/tsup.config.ts, apps/worker/src/index.ts, apps/worker/src/jobs/link-unfurl.ts
New worker project: sets up Redis emitter and BullMQ Worker, processes link-unfurl jobs (OG fetch with proxy rules, safety checks, timeout), writes message.embeds to DB, and emits message:embeds:updated.
API & Queries
apps/api/src/lib/queries/messages.ts
Default message.embeds to [] when constructing fetched message pages for a consistent public shape.
Frontend editor & deps
apps/web/package.json, apps/web/src/components/chat/composer/message-input.tsx
Add @tiptap/extension-link; configure Link extension; read raw Markdown and sanitize via toStoredMarkdown (preserve mentions, strip tiptap wrappers, handle non-breaking spaces).
Frontend rendering & realtime
apps/web/src/components/chat/embed-card.tsx, apps/web/src/components/chat/message-item.tsx, apps/web/src/hooks/use-message-sending.ts, apps/web/src/lib/realtime-adapter.ts
Add EmbedCard UI component; render embeds in messages; adapter/sending logic ensure embeds present; add socket handler for message:embeds:updated to update cached messages in real time.

Sequence Diagram

sequenceDiagram
    participant Client as Web Client
    participant Realtime as Realtime Server
    participant Queue as BullMQ Queue
    participant Worker as Worker Service
    participant DB as Database
    participant OG as OG Scraper

    Client->>Realtime: Send message with URL
    Realtime->>Client: Acknowledge message (includes embeds: [])
    Realtime->>Queue: Enqueue LinkUnfurlJob (messageId, channelId, urls)
    Queue->>Worker: Deliver job
    Worker->>OG: Fetch Open Graph metadata (proxy rules, timeout)
    OG-->>Worker: Return metadata (embed)
    Worker->>DB: Update message.embeds
    Worker->>Realtime: Emit message:embeds:updated (channelId, messageId, embeds)
    Realtime->>Client: Broadcast embed update
    Client->>Client: Update UI and render EmbedCard(s)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #9 — Updates and expands @repo/realtime-types; directly related to the new events/types and queues export.
  • PR #8 — Modifies realtime server and message handling; overlaps with queue setup and embed-related message flows.
  • PR #13 — Changes message schema (messageWithAuthorSchema) in OpenAPI; overlaps with schema additions to embeds/reactions.

Poem

🐰 I sniffed a link beneath a post,
I hopped to queue it — what a boast!
A worker fetched the shiny crumbs,
Embeds arrived — the chat now hums.
Nibble, nudge, and share a carrot-toast.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the main feature being added: link previews with OG (Open Graph) embeds and a background worker service.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/lib/helpers/openapi/message-schemas.ts`:
- Around line 19-25: The url and thumbnail fields on the messageEmbedSchema
currently accept any string; update their validators in messageEmbedSchema so
they only allow http or https URLs (e.g., replace z.string() for url and
thumbnail with a validator that enforces the scheme, such as
z.string().url().refine(u => /^https?:\/\//i.test(u)) or
z.string().regex(/^https?:\/\//i), keeping the same optionality for thumbnail
and preserving the messageEmbedSchema export).

In `@apps/realtime/src/index.ts`:
- Around line 419-434: The current parseRedisUrl function discards TLS scheme
and DB index; instead provide the raw REDIS_URL string to BullMQ so URL
semantics are preserved—replace the connection object used when creating
linkUnfurlQueue (the Queue<LinkUnfurlJobData> instance for LINK_UNFURL_QUEUE) to
pass env.REDIS_URL (or { url: env.REDIS_URL }) directly rather than
parseRedisUrl(...) and make the equivalent change for the Worker construction in
the worker setup so both Queue and Worker use the full REDIS_URL string.

In `@apps/web/src/components/chat/composer/message-input.tsx`:
- Around line 105-121: In toStoredMarkdown, the second replace uses a
character-class that breaks on '+' so wrappers around text containing '+' (like
URLs) won't be captured; update the replace call in toStoredMarkdown that
currently targets the "++...++" wrapper to instead capture any characters
(including '+' and newlines) non-greedily between the opening and closing "++"
so it matches the full wrapped content (i.e., match "++", capture any chars
until the next "++" non-greedily, then return the capture group).

In `@apps/web/src/components/chat/embed-card.tsx`:
- Around line 13-52: hasMeta currently includes embed.siteName causing the
fallback clickable URL to be suppressed when only siteName is present; change
hasMeta to only reflect embed.title or embed.description (e.g., const hasMeta =
Boolean(embed.title || embed.description)) so the fallback link block (the
anchor that renders when !hasMeta) will appear for site-name-only embeds, and
update the thumbnail rendering check (the condition using embed.thumbnail &&
hasMeta) to instead allow thumbnail when siteName exists as well (e.g.,
embed.thumbnail && (embed.title || embed.description || embed.siteName)) so
thumbnails still render correctly.

In `@apps/worker/src/index.ts`:
- Around line 32-46: Add a worker-level "error" event handler for
linkUnfurlWorker to catch connection and runtime errors that aren't tied to
individual jobs; locate where linkUnfurlWorker is instantiated (new Worker with
LINK_UNFURL_QUEUE and createLinkUnfurlProcessor(emitter)) and register
linkUnfurlWorker.on("error", handler) that logs the error (include error
stack/message and contextual info such as LINK_UNFURL_QUEUE and worker id) and
triggers any necessary alerts or shutdown logic consistent with existing failure
handling.
- Around line 9-17: The parseRedisUrl function currently only returns
host/port/username/password; update it to also detect TLS and the selected DB:
inspect parsed.protocol and set a boolean like tls (true when parsed.protocol
=== 'rediss:'), extract the database index from parsed.pathname (strip leading
'/' and parseInt, default 0 when empty/invalid), and ensure port falls back to
6379 when parsed.port is empty; update the returned object from parseRedisUrl to
include tls and db fields so callers can configure secure connections and select
the correct Redis database.

In `@apps/worker/src/jobs/link-unfurl.ts`:
- Around line 95-106: The DB update may affect zero rows if the message was
deleted; modify the logic around the update of schema.message (the call using
db.update(...).set(...).where(eq(schema.message.id, messageId))) to verify the
update affected a row before emitting the realtime payload
(RealtimeMessageEmbedsUpdated) via
emitter.to(channelRoom(channelId)).emit("message:embeds:updated", payload).
Concretely, check the result/rowCount from the update (or select the message by
messageId first) and only construct/emit the payload when the message exists
(i.e., update affected >0); otherwise skip emitting.
- Around line 34-43: The URL constructor in matchProxyRule can throw for
malformed strings; wrap the parsing/hostname substitution in a try-catch around
new URL(originalUrl) inside the loop (where OG_PROXY_RULES is iterated) and on
error return null (or continue to next rule) so the job skips invalid matches
instead of throwing; ensure you still return the { fetchUrl, siteName } on
success and leave the function returning null when parsing fails.
- Line 13: The URL_REGEX (/https?:\/\/[^\s<>")\]]+/) currently captures trailing
punctuation (.,!?:;') which breaks OG fetches; update the logic in
extractFirstUrl (or the regex constant URL_REGEX) to avoid or strip those
characters: either tighten URL_REGEX to stop before common trailing punctuation
or, after matching in extractFirstUrl, trim trailing characters like . , ! ? : ;
) ] ' " and unmatched closing punctuation before returning the URL so only the
real URL is fetched.

In `@packages/realtime-types/src/queues.ts`:
- Around line 3-6: Change the LinkUnfurlJobData type to carry only extracted
URL(s) instead of raw message text (e.g. replace content: string with urls:
string[] or firstUrl?: string) in packages/realtime-types/src/queues.ts; then
update the enqueue call in apps/realtime/src/index.ts to extract URLs from the
message before creating the job and populate the new urls field; finally update
the worker in apps/worker/src/jobs/link-unfurl.ts to read the new urls property
(use the first URL or iterate as needed) and remove any usage of message content
so raw message text is no longer stored in the queue payload.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e844f52c-92b2-4299-9a52-a94c36322965

📥 Commits

Reviewing files that changed from the base of the PR and between 44b1d14 and 0d27a18.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • apps/api/src/lib/helpers/openapi/message-schemas.ts
  • apps/api/src/lib/queries/messages.ts
  • apps/realtime/package.json
  • apps/realtime/src/index.ts
  • apps/realtime/src/services/messages.ts
  • apps/web/package.json
  • apps/web/src/components/chat/composer/message-input.tsx
  • apps/web/src/components/chat/embed-card.tsx
  • apps/web/src/components/chat/message-item.tsx
  • apps/web/src/hooks/use-message-sending.ts
  • apps/web/src/lib/realtime-adapter.ts
  • apps/worker/package.json
  • apps/worker/src/index.ts
  • apps/worker/src/jobs/link-unfurl.ts
  • apps/worker/tsconfig.json
  • apps/worker/tsup.config.ts
  • packages/db/src/schemas/messages.ts
  • packages/realtime-types/package.json
  • packages/realtime-types/src/events.ts
  • packages/realtime-types/src/index.ts
  • packages/realtime-types/src/queues.ts

Comment thread apps/api/src/lib/helpers/openapi/message-schemas.ts
Comment thread apps/realtime/src/index.ts
Comment thread apps/web/src/components/chat/composer/message-input.tsx
Comment thread apps/web/src/components/chat/embed-card.tsx Outdated
Comment thread apps/worker/src/index.ts
Comment thread apps/worker/src/index.ts
Comment thread apps/worker/src/jobs/link-unfurl.ts Outdated
Comment thread apps/worker/src/jobs/link-unfurl.ts
Comment thread apps/worker/src/jobs/link-unfurl.ts Outdated
Comment thread packages/realtime-types/src/queues.ts Outdated
- Validate embed URL fields with https?:// regex in messageEmbedSchema
- Fix parseRedisUrl in realtime and worker to preserve TLS and DB index
- Fix ++ regex in toStoredMarkdown to handle URLs containing '+' chars
- Fix hasMeta in EmbedCard so fallback link renders for siteName-only
  embeds
- Add worker-level error handler for linkUnfurlWorker
- Strip trailing punctuation from URL regex to avoid broken OG fetches
- Wrap matchProxyRule URL parsing in try-catch for malformed strings
- Check DB update result before emitting realtime embed event
- Change LinkUnfurlJobData to carry extracted URLs instead of raw
  content
- Add retention policy (removeOnComplete/removeOnFail) to unfurl queue
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/realtime/src/index.ts`:
- Around line 335-337: The current regex used in parsed.content.match (assigned
to urlMatches) prevents `)` and `]` inside URLs which truncates valid links;
update the matcher to allow `)` and `]` within the main URL match and instead
strip only trailing punctuation (like .,!?;:'")\]] ) via a trailing lookahead or
post-match trim so parentheses/brackets inside paths are preserved; modify the
regex used in parsed.content.match (or add a small post-processing step on
urlMatches) to permit internal `)` and `]` while still excluding final
punctuation from the matched URL.

In `@apps/worker/src/jobs/link-unfurl.ts`:
- Around line 42-55: The fetchOgEmbed function currently fetches user-controlled
URLs (and proxy.fetchUrl from matchProxyRule) without filtering, enabling SSRF;
update fetchOgEmbed (and the similar code at the other fetch site around lines
82-88) to validate and sanitize the target before calling ogs: parse the URL,
resolve its hostname to IP(s) (e.g., dns.lookup/dns.resolve), and reject
requests whose IPs or hostnames map to localhost, private RFC1918 ranges,
link-local, or other internal addresses; alternatively enforce that only allowed
proxy.fetchUrl values are used for untrusted inputs. Ensure validation runs on
both the original url and proxy.fetchUrl (if present) and throw/return null when
the check fails so ogs is never called with an internal address.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c1b0251d-1937-4d6b-aede-528b259f63e9

📥 Commits

Reviewing files that changed from the base of the PR and between 0d27a18 and 9d11e4f.

📒 Files selected for processing (7)
  • apps/api/src/lib/helpers/openapi/message-schemas.ts
  • apps/realtime/src/index.ts
  • apps/web/src/components/chat/composer/message-input.tsx
  • apps/web/src/components/chat/embed-card.tsx
  • apps/worker/src/index.ts
  • apps/worker/src/jobs/link-unfurl.ts
  • packages/realtime-types/src/queues.ts

Comment thread apps/realtime/src/index.ts Outdated
Comment thread apps/worker/src/jobs/link-unfurl.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/worker/src/jobs/link-unfurl.ts`:
- Around line 19-40: The isSafeUrl function currently uses lookup(hostname)
which returns only the first resolved address and can miss private IPs; update
isSafeUrl to resolve and inspect all addresses for the hostname (either by using
lookup(hostname, { all: true }) and iterating the returned records or by calling
dns.resolve4 and dns.resolve6), check every returned address against
PRIVATE_IP_REGEX, and reject if any resolved address is private; ensure the
hostname sanity checks remain (protocol, localhost, [::1]) and that errors still
cause a false result.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c38284d8-81cc-4471-8d3e-662c52e8cfe8

📥 Commits

Reviewing files that changed from the base of the PR and between 9d11e4f and 4d61943.

📒 Files selected for processing (2)
  • apps/realtime/src/index.ts
  • apps/worker/src/jobs/link-unfurl.ts

Comment thread apps/worker/src/jobs/link-unfurl.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/worker/src/jobs/link-unfurl.ts`:
- Around line 115-121: The handler currently only processes the first URL (using
urls[0]) and drops the rest; update the LinkUnfurlJobData handling to iterate
over all entries in the urls array by replacing the single-url logic with a loop
or Promise.all that calls fetchOgEmbed for each url, collects non-null results
into the embeds array, and preserves the existing early-return behavior when
embeds is empty; specifically change usages of the url variable and the embeds
assignment in link-unfurl.ts (where fetchOgEmbed and embeds are used) so that
all urls are fetched (e.g., await Promise.all(urls.map(...)), filter out nulls)
and then continue using the resulting embeds array.
- Around line 75-86: The isSafeUrl(fetchUrl) check validates only the initial
URL but ogs(...) follows redirects, allowing SSRF via post-check redirection;
update the ogs call (in the link-unfurl job) to control redirects by adding
fetchOptions.redirect = "manual" (or "error") alongside the existing headers and
timeout, or alternatively resolve and re-run isSafeUrl against the final
redirected URL before proceeding; modify the ogs(...) invocation that uses
fetchUrl and OG_FETCH_TIMEOUT_MS to include this redirect behavior to ensure
redirects are rejected or re-validated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 806c6bf1-fb89-4d37-bf4d-f351a7496065

📥 Commits

Reviewing files that changed from the base of the PR and between 4d61943 and d179c82.

📒 Files selected for processing (1)
  • apps/worker/src/jobs/link-unfurl.ts

Comment thread apps/worker/src/jobs/link-unfurl.ts
Comment thread apps/worker/src/jobs/link-unfurl.ts Outdated
@BuckyMcYolo BuckyMcYolo merged commit 349b43f into main Mar 5, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Mar 9, 2026
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant