Digital commerce & mobile app site. Read directly from the working tree — nothing on this page is invented.
| Method | Path | Input | Output | File |
|---|
| Check | Result | What it actually tells you |
|---|---|---|
| Unit / integration tests | 0 found | find . -iname "*.test.*" -o -iname "*.spec.*" (excl. node_modules) returns nothing. Contact-form validation, slug sanitization, and the sitemap builder have no coverage. |
npm run typecheck | 0 errors | tsc --noEmit on tsconfig.app.json + tsconfig.node.json — clean. Types are sound; this is not the same as behavior being tested. |
scripts/test-contentful.ts | config check, not a test | Verifies Contentful env vars are present and the space is reachable — a connectivity smoke check, not a test suite. |
/api/contentful-cache is an unauthenticated open proxy to the live Contentful APIhandleContentfulCache() takes body.request.query straight from the client and hands it to client.getEntries(body.request.query as any) — no allowlist on content_type or fields. body.request.id goes directly into client.getEntry(id). There is no auth check and no rate limit on the route in server/index.ts. Setting body.nocache: true skips the Redis cache entirely and hits Contentful's real API on every request.
Impact: anyone can scrape any content type in the Contentful space through the server's own credentials, or drive up Contentful API usage/cost by spamming nocache: true requests with distinct slug values (each becomes a new, uncached fetch).
Proposed fix (unified diff) — corrected after review flagged the first draft as non-deployable: it called an undefined isInternalCaller(), and its allowlist only covered 2 of the 7 content types actually queried in production (grep -rn "content_type" src/hooks/*.ts shows page, blogPost, footer, header, jobPosting, navGroup, navigationMenu — applying the first draft as-is would have 404'd the site's own nav and footer):
--- a/vite-redis-middleware.ts +++ b/vite-redis-middleware.ts @@ handleContentfulCache const doFetch = async (): Promise<unknown> => { const spaceId = stripEnvQuotes(...); const { host, accessToken } = getContentfulApiConfig(); + // Every content_type this proxy is actually asked for in production + // today (grep -rn "content_type" src/hooks/*.ts) — anything else is + // a caller probing the space, not a real page load. + const ALLOWED_CONTENT_TYPES = new Set([ + "page", "blogPost", "footer", "header", "jobPosting", + "navGroup", "navigationMenu", + ]); + if (body.request.method === "getEntries") { + const ct = (body.request.query as any)?.content_type; + if (!ALLOWED_CONTENT_TYPES.has(ct)) { + throw new Error("content_type not permitted"); + } + } ... }; + // nocache=true exists so the build/deploy pipeline can force a fresh + // read; a public caller has no legitimate reason to skip the cache, + // and every skip is an uncached, unrate-limited hit on real Contentful. + const INTERNAL_BYPASS_HEADER = "x-internal-cache-bypass"; + if (body.nocache === true && req.headers[INTERNAL_BYPASS_HEADER] !== process.env.CACHE_BYPASS_SECRET) { + body = { ...body, nocache: false }; + }
find . -iname "*.test.*" -o -iname "*.spec.*"No test files exist anywhere — not for the contact-form field validation (server/index.ts:356–395), not for the SEO slug sanitization regexes (server/index.ts:591, 625), not for the sitemap URL builder. tsc --noEmit passes clean, which confirms types line up, but a passing typecheck does not catch a regex like /^[\w-][\w/-]*$/ silently rejecting a valid page slug, or a phone-validation off-by-one.
Impact: any change to this validation logic ships with no automated signal that behavior changed — regressions surface as real leads dropped or pages losing their meta tags.
When doFetch() fails, handleContentfulCache catches the error and returns { error: e.message } verbatim; server/index.ts forwards that straight into a 500 JSON response with no redaction. The Contentful SDK's error messages can include environment/config hints ("access token invalid", content type names that don't exist, etc.) that are useful for debugging but not meant for an anonymous caller.
Impact: low on its own, but combined with risk #1 (arbitrary type/slug/query input) it becomes a probing surface — an attacker can use the error text as a guide to enumerate valid content types.
<script> and regex special chars (.*+?()) into the filter box — treated as plain substring match, not evaluated, no injection into the DOM (filter compares lowercase text, never uses innerHTML with the input).innerHTML reconstruction on every keystroke; switched to toggling a hidden-row class on precomputed rows instead, so the filter can't reflect input back into markup.