Files
2026-05-27 23:29:53 +01:00

6.9 KiB
Raw Permalink Blame History

OpenSpec Change: site-icons

Package: @suaveplan/site-icons Status: PROPOSED Type: new-package

Why

Vaulted needs to display website icons next to vault login items. No maintained standalone library combines multi-source favicon discovery, size-aware selection, domain-normalized caching, and MIME validation in a single tree-shakeable package. Hand-rolling this inside the Vaulted app would duplicate concerns and prevent reuse across browser extensions and desktop.

What changes

  • ADDED: SiteIconFetcher — orchestrates multi-source discovery, caching, and fallback
  • ADDED: fetchSiteIcons(domain) — fetches icons from all known sources for a domain
  • ADDED: selectBestIcon(candidates, options?) — size-aware ranking and selection
  • ADDED: IconSource — discriminated union of discovery sources (favicon, appleTouchIcon, manifestIcon, openGraph)
  • ADDED: SiteIconResult — { source: IconSource; url: string; size: IconSize | null; mimeType: string; data: Uint8Array }
  • ADDED: IconSize — { width: number; height: number }
  • ADDED: SiteIconError extends SuaveplanError
  • ADDED: FetchTimeoutError extends SiteIconError (code SITE_ICON.TIMEOUT)
  • ADDED: InvalidIconError extends SiteIconError (code SITE_ICON.INVALID)
  • ADDED: NotFoundError extends SiteIconError (code SITE_ICON.NOT_FOUND)
  • ADDED: Zod schemas for all boundary types
  • ADDED: OTel counters site_icons.fetch.total (by source, outcome) and histogram site_icons.fetch.duration_ms

Out of scope

  • Image resizing / downscaling manipulation — deferred to @suaveplan/compress-image
  • Build-time app icon generation — @suaveplan/iconz already covers this
  • DOM-parsing HTML for arbitrary <link rel="icon"> discovery beyond the well-known sources listed above (may be added later)
  • ICO file multi-image extraction (treat ICO as opaque blob)

Gap analysis: sub-standard → enterprise

Typical favicon implementations fetch a single hard-coded /favicon.ico with no caching strategy, no size-aware selection, and no graceful fallback when sources are missing or return invalid data. This leads to broken UI states, excessive network requests, and unpredictable memory usage from unvalidated image blobs. Enterprise-grade code must treat icon discovery as a resilient pipeline with normalized domain keys, TTL-bound caching, MIME validation, and a ranked fallback chain.

Security model

  • Threat model: Malicious domains serving oversized or malformed image payloads intended to exhaust memory or exploit image-parser vulnerabilities; MITM interception of icon traffic to fingerprint user vault contents.
  • Privacy guarantees: No vault metadata or credential identifiers are transmitted during icon discovery. Domain names are normalized but not obfuscated; requests are made without authentication cookies.
  • Input validation: All fetched blobs are inspected against an allow-list of MIME types and size bounds before being retained in memory or cache.
  • Side-channel resistance: Cache lookups use constant-time key comparison where feasible; request timing is not correlated with vault item count.
  • Key material handling: Not applicable — this package does not handle cryptographic keys.

Performance SLOs

  • Latency: P95 icon fetch (cache miss) < 1.5s; P95 cache hit < 5ms.
  • Throughput: Support 1,000 concurrent domain lookups without head-of-line blocking.
  • Memory bounds: In-flight blob memory capped at 10 MB per SiteIconFetcher instance; cached entries bounded by LRU count and TTL.
  • Bundle size budget: Core fetcher + selector < 8 kB gzipped; optional cache adapter tree-shakeable.

Observability plan

  • OTel spans/metrics: Counter site_icons.fetch.total by source and outcome; histogram site_icons.fetch.duration_ms; cache hit/miss ratio gauge.
  • Structured logging: JSON logs at warn for fetch timeouts, error for MIME validation failures, debug for cache hits.
  • Error telemetry: Distinct error codes (TIMEOUT, INVALID, NOT_FOUND) tagged with domain (hashed for privacy) and source.
  • Health checks: Expose SiteIconFetcher.isHealthy() returning false if > 50% of sources are consistently failing.

Dependency audit

  • Runtime deps: @suaveplan/errors, @suaveplan/otel, @suaveplan/domain-normalizer (domain key normalization), @suaveplan/cache-browser (TTL-bound icon caching), @suaveplan/mime (image MIME validation), and platform fetch / crypto.
  • Supply-chain risk: All external image parsing is deferred to platform APIs; no third-party image-decoding libraries.
  • Tree-shakeability: SiteIconFetcher, selectBestIcon, cache adapters, and fallback chains are individually tree-shakeable.
  • License: All code and bundled assets are MIT-licensed; no copyleft dependencies.

Operational readiness

  • Graceful degradation: If all sources fail, the fallback chain yields a transparent 1×1 pixel or the consumer-provided default; UI never blocks.
  • Resource limits: Configurable per-request timeout, global concurrency limit, and max blob size.
  • Error recovery: Exponential backoff for repeated source failures; circuit-breaker pattern if a source is persistently unhealthy.
  • Backpressure: Concurrent fetches limited by semaphore; excess requests queued with a max queue depth and drop policy.
  • Version compatibility: Public API surface frozen after v1; additive-only changes; deprecated APIs supported for two major versions.

Verification approach

  • 100% coverage target: All exported functions, branches, and error paths instrumented in test.
  • Property-based tests: Domain normalization, size ranking, and cache key generation tested with fast-check invariants.
  • Load/stress criteria: Sustained 1,000 concurrent fetches for 60s without memory growth > 10%.
  • Security audit checklist: MIME bypass attempts, oversized payload handling, cache poisoning via domain normalization edge cases.
  • src/fetcher/fetcher.test.ts — covers multi-source fetch, timeout, abort, MIME validation
  • src/selector/selector.test.ts — covers size-aware ranking, null-size handling, exact match
  • src/cache/cache.test.ts — covers cache hit, miss, TTL expiry, domain-normalized keys
  • src/fallback/fallback.test.ts — covers fallback chain ordering, exhaustion
  • src/errors/errors.test.ts — covers error construction, code narrowing, context shapes
  • src/errors/errors.spec-conformance.test.ts — pins strict-mode behaviors and instanceof chains
  • tests/dep-graph.test.ts — pins dependency invariants (no React, no Node-only deps)

Migration impact

None — additive only.

Impact

Downstream consumers

No dependents (verified by bunx turbo ls --filter=...@suaveplan/site-icons).

Breaking changes

None — additive only.

Workspace dep prerequisites

  • @suaveplan/errors
  • @suaveplan/otel
  • @suaveplan/domain-normalizer
  • @suaveplan/cache-browser
  • @suaveplan/mime