Skip to content

Dev#2

Merged
willymwai merged 12 commits into
mainfrom
dev
May 15, 2026
Merged

Dev#2
willymwai merged 12 commits into
mainfrom
dev

Conversation

@kipsang01
Copy link
Copy Markdown
Collaborator

No description provided.

kipsang01 added 2 commits May 9, 2026 11:26
- Updated spree index to export additional configuration functions.
- Refactored locale management to resolve spree configuration asynchronously.
- Changed default store name to "Olitt Store" in store utility.
- Added comprehensive tests for Olitt tenant configuration helpers.
- Introduced surface helpers for tenant branding, navigation, and footer configurations.
- Implemented CSS variable resolution for tenant themes.
- Created new tenant normalization and request handling utilities.
- Established a structured approach for fetching and resolving tenant configurations from Olitt API.
feat: enhance tenant configuration and surface helpers
@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

Add GitHub Actions workflow for PR comments
@willymwai
Copy link
Copy Markdown
Member

/agentic_review

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 11, 2026

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Loading() missing return type ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The Loading component is declared without an explicit return type, relying on inference. This
violates the requirement for explicit return types and reduces clarity/type-safety for exported
functions.
Code

src/app/[country]/[locale]/loading.tsx[6]

+export default async function Loading() {
Evidence
PR Compliance ID 638178 requires explicit return types on functions. The exported Loading function
in loading.tsx has no : ... return type annotation.

Rule 638178: Require explicit return types on all functions
src/app/[country]/[locale]/loading.tsx[6-6]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Loading()` is exported without an explicit return type annotation.

## Issue Context
Per the project's standards, new/modified functions should declare explicit return types (especially exported functions).

## Fix Focus Areas
- src/app/[country]/[locale]/loading.tsx[6-6]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. getRawConfig() missing return type ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
Helper functions getRawConfig, getDesignConfig, and getLayoutConfig omit explicit return type
annotations. This violates the explicit return type requirement and can mask unintended return-type
changes during refactors.
Code

src/lib/tenant/resolvers.ts[R70-79]

+function getRawConfig(config?: TenantSurfaceConfig | null) {
+  return getRecord(config?.raw);
+}
+
+function getDesignConfig(config?: TenantSurfaceConfig | null) {
+  return getRecord(getRawConfig(config)?.design);
+}
+
+function getLayoutConfig(config?: TenantSurfaceConfig | null) {
+  return getRecord(getDesignConfig(config)?.layout);
Evidence
PR Compliance ID 638178 requires explicit return types on all functions. In
src/lib/tenant/resolvers.ts, multiple newly added helper functions are missing return type
annotations in their signatures.

Rule 638178: Require explicit return types on all functions
src/lib/tenant/resolvers.ts[70-79]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`getRawConfig`, `getDesignConfig`, and `getLayoutConfig` are declared without explicit return types.

## Issue Context
These helpers are used to parse tenant config records and should have stable, explicit return types (e.g., `Record<string, unknown> | undefined`) to avoid accidental widening/narrowing.

## Fix Focus Areas
- src/lib/tenant/resolvers.ts[70-79]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Comma forwarded host rejected ✓ Resolved 🐞 Bug ☼ Reliability
Description
normalizeHeaderHost() returns null when the header value contains a comma, which is a common
multi-proxy format for X-Forwarded-Host. This can make getRequestHost() fail to resolve the
tenant host in real deployments behind multiple proxies/load balancers.
Code

src/lib/tenant/request.ts[R17-20]

+function normalizeHeaderHost(value: string | null): string | null {
+  if (!value) return null;
+  if (value.includes(",")) return null;
+  return normalizeHost(value, { preservePort: true });
Evidence
getRequestHost() uses normalizeHeaderHost() for both host and x-forwarded-host; returning
null for comma-separated values causes tenant host resolution to fail even when proxy trust is
enabled.

src/lib/tenant/request.ts[17-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`normalizeHeaderHost()` rejects comma-delimited header values. `X-Forwarded-Host` can legitimately be a comma-separated list; dropping it makes tenant resolution fail.

### Issue Context
When proxy trust is enabled, `getRequestHost()` prefers `x-forwarded-host`. The current normalization returns `null` for common chained values.

### Fix Focus Areas
- src/lib/tenant/request.ts[17-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. HeroSection uses <img> ✓ Resolved 📘 Rule violation ➹ Performance
Description
The hero media is rendered with an unoptimized <img> tag instead of next/image, losing Next.js
image optimization and risking layout shifts/perf regressions. This violates the repository standard
for image rendering in product/marketing UI.
Code

src/components/home/HeroSection.tsx[R181-188]

+              <img
+                src={
+                  section.media?.imageUrl ??
+                  "https://images.unsplash.com/photo-1523381210434-271e8be1f52b?auto=format&fit=crop&q=80&w=1200"
+                }
+                alt={section.media?.alt ?? section.title}
+                className="w-full h-full object-cover grayscale-[0.2] hover:grayscale-0 transition-all duration-700 scale-105 hover:scale-100"
+              />
Evidence
PR Compliance ID 13 requires using next/image (with explicit sizing) where appropriate. The
updated HeroSection renders the main hero image via a raw <img> tag on the added lines.

CLAUDE.md
src/components/home/HeroSection.tsx[181-188]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`HeroSection` renders a prominent hero image using a raw `<img>` element, bypassing Next.js image optimization.

## Issue Context
Compliance requires `next/image` with appropriate sizing props to improve performance and prevent layout shifts.

## Fix Focus Areas
- src/components/home/HeroSection.tsx[181-188]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Suspense fallback is null ✓ Resolved 📘 Rule violation ➹ Performance
Description
The layout wraps content in <Suspense fallback={null}>, which provides no loading UI during
suspension and can cause blank/jarring transitions. This violates the requirement to provide loading
UI via loading.tsx and/or Suspense fallbacks.
Code

src/app/[country]/[locale]/layout.tsx[R76-80]

+    <Suspense fallback={null}>
+      <CountryLocaleLayoutInner params={params}>
+        {children}
+      </CountryLocaleLayoutInner>
+    </Suspense>
Evidence
PR Compliance ID 8 requires explicit loading UI for potentially-suspending routes/components. The
added Suspense boundary uses fallback={null}, which is not a loading UI.

CLAUDE.md
src/app/[country]/[locale]/layout.tsx[76-80]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/app/[country]/[locale]/layout.tsx` introduces a Suspense boundary with a `null` fallback, which provides no visible loading state.

## Issue Context
Compliance requires a loading UI either via `loading.tsx` for the route segment and/or a Suspense fallback component (skeleton/spinner).

## Fix Focus Areas
- src/app/[country]/[locale]/layout.tsx[76-80]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Spoofable tenant host header ✓ Resolved 🐞 Bug ⛨ Security
Description
Tenant resolution prefers the request's "x-forwarded-host" header without any trust boundary check,
so in deployments where that header isn't stripped/overwritten by a trusted proxy, clients can spoof
it to select another tenant's config. This breaks tenant isolation and can lead to cross-tenant
config exposure and incorrect store behavior per request.
Code

src/lib/tenant/request.ts[R6-12]

+export async function getTenantConfigFromRequest() {
+  const requestHeaders = await headers();
+  const host =
+    requestHeaders.get("x-forwarded-host") ?? requestHeaders.get("host");
+
+  if (!host) return null;
+  return getTenantConfigByHost(host);
Evidence
Tenant lookup uses x-forwarded-host before host, and the resulting value is passed directly into
getTenantConfigByHost(host) which resolves a tenant by that host string.

src/lib/tenant/request.ts[6-12]
src/app/[country]/[locale]/layout.tsx[88-97]
src/lib/tenant/olitt.ts[227-245]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`getTenantConfigFromRequest()` and layouts prefer `x-forwarded-host` over `host` with no verification that the request came through a trusted proxy. In environments where clients can set `x-forwarded-host` directly, an attacker can force resolution of a different tenant.

### Issue Context
This is a multi-tenant boundary: the chosen host determines which tenant config is fetched and used for rendering.

### Fix Focus Areas
- Use `host` by default, and only honor `x-forwarded-host` when an explicit "trust proxy" setting is enabled (e.g., env flag) **and** the deployment guarantees stripping/overwriting of forwarded headers.
- Optionally validate the header value format (single host, no commas) and normalize it before resolution.

### Fix Focus Areas (code)
- src/lib/tenant/request.ts[6-12]
- src/app/[country]/[locale]/layout.tsx[88-97]
- src/app/[country]/[locale]/(storefront)/layout.tsx[43-48]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Tenant config client leak ✓ Resolved 🐞 Bug ⛨ Security
Description
TenantConfigProvider is a client component and is given the full server-fetched TenantConfig,
which includes paymentKeys and the full raw Olitt record, causing those fields to be serialized
to the browser. Any sensitive values present in those fields (now or in the future) will be exposed
to all users.
Code

src/app/[country]/[locale]/layout.tsx[R132-156]

  return (
-    <NextIntlClientProvider
-      messages={messages}
-      locale={locale as "en" | "de" | "pl"}
+    <div
+      style={cssVars}
+      data-tenant-host={tenantConfig.host}
+      data-tenant-id={tenantConfig.tenantId}
    >
-      <StoreProvider
-        initialCountry={country}
-        initialLocale={locale}
-        initialMarkets={markets}
-      >
-        <AuthProvider>
-          <JsonLd data={buildOrganizationJsonLd()} />
-          {children}
-          <CartDrawer />
-          <Toaster />
-        </AuthProvider>
-      </StoreProvider>
-    </NextIntlClientProvider>
+      <TenantConfigProvider config={tenantConfig}>
+        <NextIntlClientProvider
+          messages={messages}
+          locale={locale as "en" | "de" | "pl"}
+        >
+          <StoreProvider
+            initialCountry={country}
+            initialLocale={locale}
+            initialMarkets={markets}
+          >
+            <AuthProvider>
+              <JsonLd data={buildOrganizationJsonLd(tenantConfig)} />
+              {children}
+              <CartDrawer />
+              <Toaster />
+            </AuthProvider>
+          </StoreProvider>
+        </NextIntlClientProvider>
+      </TenantConfigProvider>
Evidence
The layout passes tenantConfig into a client-side context provider; the TenantConfig structure
includes paymentKeys and the full raw record, and the client hook explicitly exposes
paymentKeys.

src/app/[country]/[locale]/layout.tsx[132-157]
src/contexts/TenantContext.tsx[1-46]
src/lib/tenant/normalize.ts[163-200]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A full `TenantConfig` (including `raw` and `paymentKeys`) is passed into a client component provider, so it gets serialized into the page payload and becomes accessible in the browser.

### Issue Context
Even if current `paymentKeys` are intended to be publishable, the type/collector (`collectPaymentKeys`) will forward any string key, and `raw` forwards the entire Olitt record.

### Fix Focus Areas
- Introduce a `PublicTenantConfig` (or similar) that contains only the fields required client-side (e.g., branding/theme/navigation needed for UI), and explicitly omit `raw` and any sensitive keys.
- If client-side payment integration is needed, explicitly whitelist publishable keys (e.g., `stripePublishableKey`) rather than passing through arbitrary key/value pairs.
- Keep the full tenant record server-only.

### Fix Focus Areas (code)
- src/app/[country]/[locale]/layout.tsx[132-157]
- src/contexts/TenantContext.tsx[1-46]
- src/lib/tenant/normalize.ts[137-201]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

8. SEO env vars ignored 🐞 Bug ≡ Correctness
Description
getStoreSeoTitle() and getStoreMetaDescription() no longer read STORE_SEO_TITLE /
STORE_META_DESCRIPTION, so deployments setting those env vars will see no effect on generated
metadata. This also leaves the inline docstrings and .env.example misleading because they still
describe those variables as supported.
Code

src/lib/store.ts[R68-77]

export function getStoreSeoTitle(): string {
-  return process.env.STORE_SEO_TITLE || getStoreName();
+  return getStoreName();
}

/**
 * Get the meta description, preferring STORE_META_DESCRIPTION and falling
 * back to the store description (NEXT_PUBLIC_STORE_DESCRIPTION).
 */
export function getStoreMetaDescription(): string {
-  return process.env.STORE_META_DESCRIPTION || getStoreDescription();
+  return getStoreDescription();
Evidence
The store helpers still claim they prefer STORE_SEO_TITLE/STORE_META_DESCRIPTION, and
.env.example still provides those knobs, but the implementation now ignores them; metadata
generation uses these helpers as the fallback path, so configured env values won’t propagate.

src/lib/store.ts[64-78]
.env.example[18-21]
src/lib/metadata/store.ts[28-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`getStoreSeoTitle()` and `getStoreMetaDescription()` were changed to always return `getStoreName()` / `getStoreDescription()`, but both the function docstrings and `.env.example` still indicate `STORE_SEO_TITLE` and `STORE_META_DESCRIPTION` are supported. This causes silent misconfiguration where env var overrides are ignored.

### Issue Context
`generateStoreMetadata()` falls back to `getStoreSeoTitle()` / `getStoreMetaDescription()` when tenant config lacks values, so these store-level env vars are still relevant as defaults.

### Fix Focus Areas
- src/lib/store.ts[64-78]

### Suggested fix
- Re-introduce env overrides:
 - `return process.env.STORE_SEO_TITLE || getStoreName();`
 - `return process.env.STORE_META_DESCRIPTION || getStoreDescription();`
- If the removal was intentional, then instead update the docstrings and `.env.example` to remove/clarify those variables so configuration does not silently do nothing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Unsplash pattern lacks pathname 🐞 Bug ☼ Reliability
Description
The new images.remotePatterns entry for images.unsplash.com omits pathname, unlike the
existing patterns, which can cause the allowlist to not match (or config validation failures
depending on Next.js config parsing). This can break rendering/optimization of the default Hero
image which uses images.unsplash.com.
Code

next.config.ts[R60-63]

+      {
+        protocol: "https",
+        hostname: "images.unsplash.com",
+      },
Evidence
The Unsplash pattern is the only one missing pathname, and the Hero uses an Unsplash URL through
next/image, which depends on the images.remotePatterns allowlist.

next.config.ts[41-64]
src/components/home/HeroSection.tsx[176-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`next.config.ts` adds an `images.remotePatterns` entry for `images.unsplash.com` without a `pathname`. All other patterns specify a pathname, and Next image allowlisting commonly expects it; this can lead to the Unsplash image not being permitted.

### Issue Context
Hero uses a default Unsplash URL via `next/image`, so this allowlist must reliably match.

### Fix Focus Areas
- next.config.ts[60-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Hero image host may fail 🐞 Bug ≡ Correctness
Description
HeroSection feeds section.media?.imageUrl directly into next/image, but the hostname must be
present in images.remotePatterns or Next will throw and the hero won’t render. Homepage config can
be derived from tenant raw design/layout, so tenant-provided image URLs can easily reference
non-allowlisted hosts.
Code

src/components/home/HeroSection.tsx[R182-191]

+              <Image
+                src={
+                  section.media?.imageUrl ??
+                  "https://images.unsplash.com/photo-1523381210434-271e8be1f52b?auto=format&fit=crop&q=80&w=1200"
+                }
+                alt={section.media?.alt ?? section.title}
+                fill
+                priority
+                sizes="(min-width: 1024px) 50vw, 100vw"
+                className="object-cover grayscale-[0.2] hover:grayscale-0 transition-all duration-700 scale-105 hover:scale-100"
Evidence
Hero uses section.media?.imageUrl as the image source. Homepage types permit arbitrary URLs, and
homepage config is derived from tenant-provided raw design/layout; Olitt payload tests show
design.layout is present in tenant records, making such overrides plausible.

src/components/home/HeroSection.tsx[176-193]
src/lib/homepage/types.ts[30-51]
src/lib/homepage/index.ts[135-156]
src/lib/tenant/normalize.ts[178-215]
src/lib/tenant/tests/olitt.test.ts[101-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`next/image` requires allowlisted remote hosts. `HeroSection` uses a potentially tenant-configurable `section.media.imageUrl`, which can point to non-allowlisted domains and crash the component at runtime.

### Issue Context
Homepage config supports tenant overrides derived from tenant `raw` (design/layout). Olitt tenant payloads include `design.layout` data.

### Fix Focus Areas
- src/components/home/HeroSection.tsx[182-191]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Unbounded client cache 🐞 Bug ☼ Reliability
Description
resolvedClients caches Spree clients in a module-level Map with no size limit or eviction, so
memory can grow with each unique tenant/config combination in long-running processes. This can
become a memory/DoS risk as tenant count or config churn increases.
Code

src/lib/spree/config.ts[R8-54]

+const resolvedClients = new Map<string, Client>();
+
+function buildEnvConfig(): SpreeNextConfig {
+  const baseUrl = process.env.SPREE_API_URL;
+  const publishableKey = process.env.SPREE_PUBLISHABLE_KEY;
+
+  if (!baseUrl || !publishableKey) {
+    throw new Error(
+      "Spree client is not configured. Either call initSpreeNext() or set SPREE_API_URL and SPREE_PUBLISHABLE_KEY environment variables.",
+    );
+  }
+
+  return {
+    baseUrl,
+    publishableKey,
+    defaultCountry: (
+      process.env.NEXT_PUBLIC_DEFAULT_COUNTRY || "us"
+    ).toLowerCase(),
+    defaultLocale: process.env.NEXT_PUBLIC_DEFAULT_LOCALE || "en",
+  };
+}
+
+function getBaseConfig(): SpreeNextConfig {
+  return _config ?? buildEnvConfig();
+}
+
+function getClientCacheKey(config: SpreeNextConfig): string {
+  return `${config.baseUrl}::${config.publishableKey}`;
+}
+
+export function getClientForConfig(config: SpreeNextConfig): Client {
+  const cacheKey = getClientCacheKey(config);
+  const existingClient = resolvedClients.get(cacheKey);
+
+  if (existingClient) {
+    return existingClient;
+  }
+
+  const client = createClient({
+    baseUrl: config.baseUrl,
+    publishableKey: config.publishableKey,
+  });
+
+  resolvedClients.set(cacheKey, client);
+
+  return client;
+}
Evidence
The cache is module-scoped, keyed by baseUrl+publishableKey, and only cleared via resetClient()
(test utility). Tenant config is resolved per request, so multi-tenant deployments can introduce
many unique cache keys over time.

src/lib/spree/config.ts[8-54]
src/lib/spree/config.ts[56-71]
src/lib/spree/config.ts[150-154]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`resolvedClients` is an unbounded in-memory cache of Spree clients and can grow without limit in a multi-tenant, long-running server.

### Issue Context
This may be acceptable in serverless (short-lived) runtimes, but is risky for containers/VMs where the process lives for days.

### Fix Focus Areas
- Use an LRU/TTL cache for clients (e.g., cap to N tenants, evict least-recently-used).
- Normalize `baseUrl` before computing the cache key (avoid accidental key multiplicity due to trivial URL differences).
- Add minimal observability (e.g., log when cache exceeds threshold) if eviction isn’t implemented.

### Fix Focus Areas (code)
- src/lib/spree/config.ts[8-54]
- src/lib/spree/config.ts[34-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Sequential awaits in DynamicStorePage 📘 Rule violation ➹ Performance
Description
Independent server data reads are awaited sequentially (resolveCurrency then
getTenantConfigFromRequest), increasing total latency versus running them concurrently. This
conflicts with the requirement to parallelize independent server fetching with Promise.all.
Code

src/app/[country]/[locale]/(storefront)/pages/[...slug]/page.tsx[R47-48]

+  const currency = await resolveCurrency(country);
+  const tenantConfig = await getTenantConfigFromRequest();
Evidence
PR Compliance ID 7 requires parallelizing independent server fetches. In the new dynamic page,
resolveCurrency(country) and getTenantConfigFromRequest() are awaited one after the other on
added lines, rather than using Promise.all.

CLAUDE.md
src/app/[country]/[locale]/(storefront)/pages/[...slug]/page.tsx[47-48]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The page performs sequential awaits for independent data needs, increasing TTFB.

## Issue Context
Use `Promise.all([...])` when fetches do not depend on each other.

## Fix Focus Areas
- src/app/[country]/[locale]/(storefront)/pages/[...slug]/page.tsx[47-48]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Prototype pollution merge risk 🐞 Bug ⛨ Security
Description
Homepage config interpolation/merging processes tenantConfig.raw with Object.fromEntries and
assigns arbitrary keys into plain objects without filtering __proto__/constructor-style keys. A
malicious or compromised tenant config payload can introduce prototype-pollution keys and cause
unpredictable runtime behavior.
Code

src/lib/homepage/index.ts[R40-73]

+  if (value && typeof value === "object") {
+    return Object.fromEntries(
+      Object.entries(value).map(([key, entry]) => [
+        key,
+        interpolateValue(entry, variables),
+      ]),
+    ) as T;
+  }
+
+  return value;
+}
+
+function mergeObjects<T>(base: T, override: unknown): T {
+  if (!isRecord(base) || !isRecord(override)) {
+    return (override ?? base) as T;
+  }
+
+  const result: Record<string, unknown> = { ...base };
+
+  for (const [key, overrideValue] of Object.entries(override)) {
+    const baseValue = result[key];
+
+    if (Array.isArray(overrideValue)) {
+      result[key] = overrideValue;
+      continue;
+    }
+
+    if (isRecord(baseValue) && isRecord(overrideValue)) {
+      result[key] = mergeObjects(baseValue, overrideValue);
+      continue;
+    }
+
+    result[key] = overrideValue;
+  }
Evidence
tenantConfig.raw is passed as the config source for homepage rendering, and homepage
interpolation/merge rebuilds/merges objects using unfiltered keys into plain objects
(Object.fromEntries + result[key] = ...). The tenant raw object is the full external Olitt
record.

src/app/[country]/[locale]/(storefront)/page.tsx[75-83]
src/lib/homepage/index.ts[40-47]
src/lib/homepage/index.ts[52-73]
src/lib/tenant/normalize.ts[184-200]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Homepage config merging/interpolation accepts arbitrary object keys from `tenantConfig.raw` and merges them into plain objects, enabling prototype-pollution keys like `__proto__`, `prototype`, or `constructor`.

### Issue Context
This config ultimately influences rendering paths. Even if the Olitt API is trusted, hardening prevents surprises if upstream payloads change or are compromised.

### Fix Focus Areas
- When building objects from entries and when merging, skip dangerous keys: `__proto__`, `prototype`, `constructor`.
- Consider using `Object.create(null)` for intermediate objects used purely as dictionaries.
- Apply the same hardening to both homepage and page-builder modules if they share the same merge patterns.

### Fix Focus Areas (code)
- src/lib/homepage/index.ts[40-76]
- src/lib/page-builder/index.ts[13-103]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/components/home/HeroSection.tsx Outdated
Comment thread src/app/[country]/[locale]/layout.tsx Outdated
Comment thread src/lib/tenant/request.ts
Comment thread src/app/[country]/[locale]/layout.tsx
@willymwai
Copy link
Copy Markdown
Member

/agentic_review

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 15, 2026

Persistent review updated to latest commit 039def0

Comment thread src/app/[country]/[locale]/loading.tsx Outdated
Comment thread src/lib/tenant/resolvers.ts Outdated
Comment thread src/lib/tenant/request.ts Outdated
kipsang01 added 2 commits May 15, 2026 14:18
feat: simplify CartProvider usage by removing fallback component in S…
@willymwai
Copy link
Copy Markdown
Member

/agentic_review

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 15, 2026

Persistent review updated to latest commit 7dec80d

@willymwai
Copy link
Copy Markdown
Member

/agentic_review

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 15, 2026

Persistent review updated to latest commit 1cd5003

@willymwai willymwai merged commit 979991f into main May 15, 2026
5 checks passed
@willymwai willymwai deleted the dev branch May 15, 2026 12:36
@willymwai willymwai restored the dev branch May 15, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants