Visitar URL original
feat(favicons): one server route for every site icon, with parent-domain fallback by waleedlatif1 · Pull Request #9494 · simstudioai/sim · GitHub
Skip to content

feat(favicons): one server route for every site icon, with parent-domain fallback - #9494

Merged
waleedlatif1 merged 4 commits into
stagingfrom
feat/favicon-service
Oct 31, 2026
Merged

waleedlatif1 merged 4 commits into
stagingfrom
feat/favicon-service

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Every site icon in the app now comes from one place, and a site Google can't see no longer renders Google's grey placeholder globe.

Why: icons were <img src="https://www.google.com/s2/favicons?..."> in the browser. For a host Google has no icon for (a private tenant such as <tenant>.atlassian.net, an internal host), it answers 404 with a 16px globe image, which the browser treats as a successful load, so onError never fired and every such row showed the placeholder. One surface also fetched <site>/favicon.ico straight from the destination.

Resolution (lib/favicon)

  • GET /api/favicon?host= asks Google, then DuckDuckGo, server-side, where the 404 is visible (the approach Bitwarden's and Linkwarden's icon services use); the second provider only learns a host the first had no icon for
  • A host with no icon walks up to its parent domain, stopping before the ICANN public suffix (via tldts), so x.gitlab.io → gitlab.io and x.atlassian.net → atlassian.net, never io or co.uk; at most 3 parents
  • Internal hosts (IP literals, single labels, names outside public DNS, .arpa) and names DNS can't hold (punctuation that could reshape a provider URL, empty labels) never leave the server; the destination itself is never contacted
  • Hits are sniffed from their bytes (shared sniffImageContentType plus ICO), never SVG, capped at 256 KB, and served nosniff
  • Only a provider 404/410 is "no icon"; any other failure is unavailable. Redis cache per host: 30 days for an icon, 1 day for none, 5 minutes after an outage; a parent resolved for one subdomain is reused by its siblings; concurrent lookups share one resolution
  • No icon is an ordinary answer: 204 (an <img> still reports it as failed and renders the fallback), not an error status

Route and client

  • Session-only operation (favicons.get, capability: 'none') through defineInternalBinaryRoute, with a per-user rate limit. Binary internal routes now accept the same user rate-limit policy JSON routes do, and may declare several success statuses (validation shared with the JSON builder) and present a 204
  • <Favicon host fallback> and faviconSrc(host) replace faviconUrl (deleted) in chat source icons, browser tab / suggestion / agent icons, table URL cells, and Sim Links; browser-imported icons stay local, with their link fallback restored on error

Type of Change

  • New feature

Testing

  • lib/favicon/{hosts,probe}.test.ts (49), written red-first from the failure modes: internal hosts never offered, tenant → platform walk, never a bare suffix, 3-parent cap, normalization and IDN, URL-reshaping punctuation and empty labels rejected (verified to fail with tldts hostname validation off); 404-with-placeholder, HTML, SVG, empty, and oversized bodies are misses; 403/408/429/5xx are unavailable
  • Route builder suite, source-icon lookalike tests, and the browser-agent icon test (398 total)
  • End-to-end against the running app with made-up hosts (12/12): signed-out 401; public site 200 with nosniff and private caching; made-up Atlassian and GitLab tenants get their platform's icon (~0.6 s cold); unknown site, internal hosts, and a URL-injection host answer 204 fast; malformed host 400; repeat lookup served from cache in ~20 ms. Not committed, since it depends on live third-party providers
  • Headless browser QA of Sim Links: tenant rows show the platform icon, unknown hosts the app's globe, no page errors
  • biome, check:api-validation:strict, check:source-conventions, check:client-boundary, check:boundaries, check:application-graph, check:permission-group-enforcement, check:route-verbs, check:file-names, check:comment-hygiene, check:utils, check:test-patterns, check:source-text

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

… parent-domain fallback

Icons were requested straight from Google in the browser, which shows Google's
placeholder globe for any host it can't see (a 404 with an image body that an
<img> treats as success), so private tenants and internal hosts rendered a
generic placeholder. GET /api/favicon asks Google and DuckDuckGo server-side,
where the 404 is visible, walks up to the parent domain so a tenant shows its
platform's icon, never sends internal hosts or the destination itself anything,
and caches results in Redis. Every surface now uses the shared <Favicon> or
faviconSrc, and binary internal routes accept a user rate limit.
@vercel

vercel Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Oct 31, 2026 3:25am UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 20 files

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/lib/favicon/probe.ts Resolved
Comment thread apps/sim/lib/favicon/hosts.ts Resolved
@greptile-apps

greptile-apps Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no new actionable issues were found.

Summary

Site icons now use one signed-in server route with provider fallback, parent-domain lookup, and caching.

  • Sim serves site icons through one route with parent-domain fallback.
  • App icon surfaces use Sim's shared route and their own fallbacks.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Site icon] --> B[Signed-in favicon route]
  B --> C[Check host]
  C -->|Internal or invalid| D[204: show fallback]
  C -->|Public| E[Check cache]
  E -->|Cached| F[Return saved result]
  E -->|Not cached| G[Try Google, then DuckDuckGo]
  G -->|Icon found| H[Cache and return image]
  G -->|No usable icon| I[Try allowed parent domains]
  I -->|Icon found| H
  I -->|None available| D
Loading

Reviews (4) · Last reviewed commit: "fix(favicons): annotate the presenter so..." · Reviewed by Greptile

Comment thread apps/sim/lib/favicon/resolve.server.ts Resolved
…mpty labels and non-DNS hostnames

A miss is an ordinary answer, so it returns 204 (which an img still reports as
failed) instead of an error status; binary internal routes may now declare
several success statuses and present a no-content response, sharing the JSON
builder's status validation. Providers are asked one at a time so a second only
learns a host the first had no icon for, a non-200 body is never read, and
anything but 404/410 counts as unavailable rather than a cached miss.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 23 files

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/lib/favicon/hosts.ts Resolved
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 23 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Turn on auto-fix | Re-trigger cubic

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 23 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Turn on auto-fix | Re-trigger cubic

@waleedlatif1
waleedlatif1 merged commit 17389db into staging Oct 31, 2026
75 checks passed
@waleedlatif1
waleedlatif1 deleted the feat/favicon-service branch October 31, 2026 03:43

This branch was previously deployed

1 inactive deployment
Preview — ddc6806f Deployed Oct 31, 2026 by vercel[bot]
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