Skip to content

fix(dashboard): guard provider icon lookups against prototype collisions - #11880

Merged
diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
NoxzRCW:fix/provider-icon-prototype-collision-11853
Aug 28, 2026
Merged

diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.51from
NoxzRCW:fix/provider-icon-prototype-collision-11853

Conversation

@NoxzRCW

@NoxzRCW NoxzRCW commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

The providers dashboard intermittently rendered the red "Failed to load providers, check your connection and try again" card while the server was healthy and its logs were clean. The card comes from the App Router error boundary at src/app/(dashboard)/dashboard/providers/error.tsx, which catches any uncaught client-side throw under /dashboard/providers/**, so the copy blaming the network is misleading. No request has to fail to produce it.

The throw is in getLobeProviderIcon():

const iconKey = LOBE_PROVIDER_ALIASES[providerId.toLowerCase()];
if (!iconKey) return null;
const entry = LOBE_ICON_COMPONENTS[iconKey];   // undefined
return type === "color" && entry.color ? entry.color : entry.mono;   // throws

Both maps are plain object literals indexed with no own-property check. A provider id whose lowercased form is an Object.prototype member resolves through the prototype chain, so LOBE_PROVIDER_ALIASES["constructor"] returns the Object constructor. That is truthy, the falsy guard lets it through, and the follow-up lookup is undefined. Reading .color off it throws Cannot read properties of undefined (reading 'color'), which matches the minified console trace exactly.

ProviderIcon calls this for every provider card, so a single such id takes the whole page down.

Worth noting for anyone reading the original report: only constructor and __proto__ can actually reach this. Every other Object.prototype member is camelCase and stops colliding once .toLowerCase() has run, so valueOf, hasOwnProperty and friends already returned null safely. I checked this at runtime rather than by inspection.

The fix adds Object.hasOwn() guards on both lookups plus a defensive entry check, and returns null for a non-string id instead of throwing on .toLowerCase().

Related Issues

Validation

  • Change type: UI
  • Focused tests and category gates from the golden path
  • npm run lint
  • Reconciled with the current active release base; focused checks rerun afterward
  • Production-code changes include a new or updated automated test in this PR
node --import tsx/esm --test tests/unit/lobe-provider-icons-prototype-collision-11853.test.ts \
  tests/unit/provider-icon-devin-desktop.test.ts tests/unit/qwen-web-retirement.test.ts
# tests 11 / pass 11 / fail 0

npx vitest run --config vitest.config.ts tests/unit/ui/lobe-provider-icons-anysearch.test.tsx \
  tests/unit/ui/lobe-provider-icons-stepfun.test.tsx
# 2 files / 3 tests passed

npm run check:dashboard-typecheck   # OK, 219 pre-existing errors, all within frozen baseline
npm run lint                        # clean

I also swept all 171 real ids in LOBE_PROVIDER_ALIASES in both color and mono modes, before and after the change, and got zero behavioural differences. That sweep is not in the committed test since it would just restate the alias table, but the committed test does cover the known-provider and unknown-provider paths.

Tests Added Or Updated

  • tests/unit/lobe-provider-icons-prototype-collision-11853.test.ts (new)

Coverage Notes

The new file covers getLobeProviderIcon() end to end: the two colliding ids in both icon modes, the camelCase prototype members that were already safe, a known provider, an unknown provider, and a non-string id.

Reviewer Notes

Same unguarded-lookup pattern exists in PROVIDER_ICON_ALIASES, LOCAL_SVG_ALIASES and THEMED_SVGS, which ProviderIcon also consults. I kept this PR to the one map that actually throws today so the change stays reviewable. Happy to follow up on the other three if you want them hardened in the same pass.

There is a second, unrelated crash on the same page that I did not touch here: opening /dashboard/providers/<id> for an id missing from the dashboard catalog makes resolveDashboardProviderInfo return null, which then gets spread into providerInfo, and a downstream providerId.toLowerCase() throws. Same error card, different signature (reading 'toLowerCase'). Say the word and I will open a separate issue for it.

getLobeProviderIcon indexed two object literals with no own-property check. A
provider id whose lowercased form is an Object.prototype member resolves
through the prototype chain: LOBE_PROVIDER_ALIASES["constructor"] returns the
Object constructor, which is truthy, so the existing falsy guard lets it
through. The follow-up LOBE_ICON_COMPONENTS lookup is then undefined and
entry.color throws.

ProviderIcon calls this for every card, so one such provider id takes the whole
providers page down through the App Router error boundary, which shows a
"check your connection" card even though nothing was wrong with the network.

Only constructor and __proto__ can reach it. Every other Object.prototype
member is camelCase and stops colliding after toLowerCase().

Closes diegosouzapw#11853
@NoxzRCW
NoxzRCW requested a review from diegosouzapw as a code owner August 28, 2026 11:17
@diegosouzapw
diegosouzapw merged commit d846692 into diegosouzapw:release/v3.8.51 Aug 28, 2026
3 checks passed
diegosouzapw added a commit that referenced this pull request Aug 28, 2026
…sions (#11920 port) (#11935)

Ports the 3 still-needed guards from #11920 that #11880 didn't cover. 90/90 + 4/4 focused tests passing.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ons (diegosouzapw#11880)

getLobeProviderIcon() indexed two plain-object maps with no own-property check — a provider id that lowercases to an Object.prototype member (e.g. constructor) resolved through the prototype chain and threw on the follow-up .color/.mono lookup, surfacing as the misleading 'Failed to load providers, check your connection' error boundary card with a healthy server and clean logs. Thanks for the precise root-cause trace!
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…sions (diegosouzapw#11920 port) (diegosouzapw#11935)

Ports the 3 still-needed guards from diegosouzapw#11920 that diegosouzapw#11880 didn't cover. 90/90 + 4/4 focused tests passing.
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.

fix: providers page crashes reading 'color' on prototype-colliding provider id, shows misleading connection-error card

2 participants