From ad40700d3dfba45399c4866fef297f7334d6886e Mon Sep 17 00:00:00 2001 From: Sarthak Agrawal Date: Sun, 4 Oct 2026 09:37:48 +0530 Subject: [PATCH 1/4] fix(swe): restore catalog-bound feedback launcher --- src/components/saasmaker-feedback.test.tsx | 91 ++++++++++++++++++++++ src/components/saasmaker-feedback.tsx | 50 +++++++++++- 2 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 src/components/saasmaker-feedback.test.tsx diff --git a/src/components/saasmaker-feedback.test.tsx b/src/components/saasmaker-feedback.test.tsx new file mode 100644 index 00000000..7945b5e5 --- /dev/null +++ b/src/components/saasmaker-feedback.test.tsx @@ -0,0 +1,91 @@ +// @vitest-environment happy-dom + +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { fetchFeedbackProjectKey, SaaSMakerFeedback } from './saasmaker-feedback'; + +const { TEST_KEY } = vi.hoisted(() => ({ TEST_KEY: 'pk_testfixture123' })); + +vi.mock('@saas-maker/feedback', async () => { + const React = await import('react'); + return { + FeedbackWidget: ({ projectId }: { projectId: string }) => + React.createElement('div', { + 'data-testid': 'feedback-widget', + 'data-project-bound': String(projectId === TEST_KEY), + }), + }; +}); + +describe('SaaS Maker feedback binding', () => { + let container: HTMLDivElement; + let root: Root; + + beforeEach(() => { + container = document.createElement('div'); + document.body.append(container); + root = createRoot(container); + vi.stubGlobal('fetch', vi.fn()); + }); + + afterEach(async () => { + await act(async () => root.unmount()); + container.remove(); + vi.unstubAllGlobals(); + }); + + it('mounts the existing widget only with the catalog-bound publishable key', async () => { + vi.mocked(fetch).mockResolvedValue({ + ok: true, + json: async () => ({ api_key: TEST_KEY }), + } as Response); + + await act(async () => root.render()); + for ( + let attempt = 0; + attempt < 5 && !container.querySelector('[data-testid="feedback-widget"]'); + attempt += 1 + ) { + await act(async () => new Promise((resolve) => setTimeout(resolve, 0))); + } + + expect(fetch).toHaveBeenCalledWith( + 'https://api.sassmaker.com/v1/capture-config/swe-interview-prep', + expect.objectContaining({ headers: { accept: 'application/json' } }) + ); + expect(container.querySelector('[data-project-bound="true"]')).not.toBeNull(); + }); + + it.each([ + ['invalid key shape', { ok: true, json: async () => ({ api_key: 'wrong-project' }) }], + ['invented publishable suffix', { ok: true, json: async () => ({ api_key: 'pk_testfixture123_publishable' }) }], + ['unavailable binding', { ok: false, json: async () => ({}) }], + ])('fails closed for %s', async (_case, response) => { + vi.mocked(fetch).mockResolvedValue(response as Response); + const key = await fetchFeedbackProjectKey(new AbortController().signal); + expect(key).toBeNull(); + }); + + it('fails closed when the public config request errors', async () => { + vi.mocked(fetch).mockRejectedValue(new Error('network unavailable')); + const key = await fetchFeedbackProjectKey(new AbortController().signal); + expect(key).toBeNull(); + }); + + it('aborts the public config request when the existing widget unmounts', async () => { + let requestSignal: AbortSignal | undefined; + vi.mocked(fetch).mockImplementation((_input, init) => { + requestSignal = init?.signal as AbortSignal; + return new Promise((_resolve, reject) => { + requestSignal?.addEventListener('abort', () => reject(new DOMException('Aborted', 'AbortError'))); + }); + }); + + await act(async () => root.render()); + expect(requestSignal?.aborted).toBe(false); + await act(async () => root.render(null)); + expect(requestSignal?.aborted).toBe(true); + }); +}); diff --git a/src/components/saasmaker-feedback.tsx b/src/components/saasmaker-feedback.tsx index 38ec70c7..92f555c6 100644 --- a/src/components/saasmaker-feedback.tsx +++ b/src/components/saasmaker-feedback.tsx @@ -1,9 +1,11 @@ import '@saas-maker/feedback/dist/index.css'; -import { lazy, Suspense } from 'react'; +import { lazy, Suspense, useEffect, useState } from 'react'; -const API_KEY = import.meta.env.VITE_SAASMAKER_API_KEY ?? ''; const API_BASE = 'https://api.sassmaker.com'; +const CATALOG_ID = 'swe-interview-prep'; +const PUBLISHABLE_KEY_PATTERN = /^pk_[a-z0-9]+$/; +const CONFIG_TIMEOUT_MS = 8_000; const FeedbackWidget = lazy(async () => { const mod = await import('@saas-maker/feedback'); @@ -11,11 +13,31 @@ const FeedbackWidget = lazy(async () => { }); export function SaaSMakerFeedback() { - if (!API_KEY) return null; + const [projectKey, setProjectKey] = useState(null); + + useEffect(() => { + const controller = new AbortController(); + let active = true; + const timeout = window.setTimeout(() => controller.abort(), CONFIG_TIMEOUT_MS); + + void fetchFeedbackProjectKey(controller.signal) + .then((key) => { + if (active && key) setProjectKey(key); + }) + .finally(() => window.clearTimeout(timeout)); + + return () => { + active = false; + window.clearTimeout(timeout); + controller.abort(); + }; + }, []); + + if (!projectKey) return null; return ( ); } + +export async function fetchFeedbackProjectKey(signal: AbortSignal): Promise { + try { + const response = await fetch(`${API_BASE}/v1/capture-config/${CATALOG_ID}`, { + headers: { accept: 'application/json' }, + signal, + }); + if (!response.ok) return null; + + const config: unknown = await response.json(); + if (typeof config !== 'object' || config === null) return null; + + const projectKey = (config as { api_key?: unknown }).api_key; + return typeof projectKey === 'string' && PUBLISHABLE_KEY_PATTERN.test(projectKey) + ? projectKey + : null; + } catch { + return null; + } +} From 171f79f2e298d472ee23a881456e60842e1d9cd6 Mon Sep 17 00:00:00 2001 From: Sarthak Agrawal Date: Sun, 4 Oct 2026 09:43:22 +0530 Subject: [PATCH 2/4] style(swe): format feedback binding test --- src/components/saasmaker-feedback.test.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/saasmaker-feedback.test.tsx b/src/components/saasmaker-feedback.test.tsx index 7945b5e5..d0489bb4 100644 --- a/src/components/saasmaker-feedback.test.tsx +++ b/src/components/saasmaker-feedback.test.tsx @@ -60,7 +60,10 @@ describe('SaaS Maker feedback binding', () => { it.each([ ['invalid key shape', { ok: true, json: async () => ({ api_key: 'wrong-project' }) }], - ['invented publishable suffix', { ok: true, json: async () => ({ api_key: 'pk_testfixture123_publishable' }) }], + [ + 'invented publishable suffix', + { ok: true, json: async () => ({ api_key: 'pk_testfixture123_publishable' }) }, + ], ['unavailable binding', { ok: false, json: async () => ({}) }], ])('fails closed for %s', async (_case, response) => { vi.mocked(fetch).mockResolvedValue(response as Response); @@ -79,7 +82,9 @@ describe('SaaS Maker feedback binding', () => { vi.mocked(fetch).mockImplementation((_input, init) => { requestSignal = init?.signal as AbortSignal; return new Promise((_resolve, reject) => { - requestSignal?.addEventListener('abort', () => reject(new DOMException('Aborted', 'AbortError'))); + requestSignal?.addEventListener('abort', () => + reject(new DOMException('Aborted', 'AbortError')) + ); }); }); From 53e5f988e5eacdc766dbb1e6ad53ae725639cd20 Mon Sep 17 00:00:00 2001 From: Sarthak Agrawal Date: Sun, 4 Oct 2026 09:57:07 +0530 Subject: [PATCH 3/4] Bound braces advisory exception to known tooling path --- docs/operations/deploy.md | 2 +- package.json | 2 +- scripts/check-code-health.mjs | 7 ++- scripts/dependency-audit-policy.mjs | 30 ++++++++++++ scripts/dependency-audit-policy.test.mjs | 62 ++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 scripts/dependency-audit-policy.mjs create mode 100644 scripts/dependency-audit-policy.test.mjs diff --git a/docs/operations/deploy.md b/docs/operations/deploy.md index cd1946e3..8d99fe13 100644 --- a/docs/operations/deploy.md +++ b/docs/operations/deploy.md @@ -44,7 +44,7 @@ When dispatched, `deploy.yml`: | `CLOUDFLARE_API_TOKEN` | Secret | Wrangler deploy (Pages Edit) | | `CLOUDFLARE_ACCOUNT_ID` | Variable | From Cloudflare dashboard | | `VITE_GOOGLE_CLIENT_ID` | Secret | Baked into SPA at build | -| `VITE_SAASMAKER_API_KEY` | Secret | Feedback widget (optional) | +| `VITE_SAASMAKER_API_KEY` | Secret | No longer required for the feedback launcher; it binds through SaaS Maker's public catalog capture-config endpoint | **Cloudflare Pages secrets** (runtime — set via `pnpm sync:pages-secrets`): diff --git a/package.json b/package.json index b48446b4..06a89cf9 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "quality:complexity": "node scripts/check-code-health.mjs complexity", "quality:duplication": "node scripts/check-code-health.mjs duplication", "quality:cycles": "node scripts/check-code-health.mjs cycles", - "quality:dependencies": "node scripts/check-code-health.mjs dependencies", + "quality:dependencies": "node --test scripts/dependency-audit-policy.test.mjs && node scripts/check-code-health.mjs dependencies", "quality:suppressions": "node scripts/check-code-health.mjs suppressions", "quality:hygiene": "node scripts/check-code-health.mjs hygiene", "quality": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test:coverage && pnpm quality:unused && pnpm quality:complexity && pnpm quality:duplication && pnpm quality:cycles && pnpm quality:dependencies && pnpm quality:suppressions && pnpm docs:validate && pnpm build && pnpm size && pnpm quality:hygiene" diff --git a/scripts/check-code-health.mjs b/scripts/check-code-health.mjs index cb910239..b78b3dc2 100644 --- a/scripts/check-code-health.mjs +++ b/scripts/check-code-health.mjs @@ -6,6 +6,7 @@ import { tmpdir } from 'node:os'; import { dirname, extname, join, relative, resolve } from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +import { isTemporarilyAcceptedBracesAdvisory } from './dependency-audit-policy.mjs'; const currentFile = fileURLToPath(import.meta.url); const projectRoot = resolve(dirname(currentFile), '..'); @@ -237,7 +238,11 @@ function checkDependencies() { const severe = Object.values(report.advisories ?? {}).filter((advisory) => ['critical', 'high'].includes(advisory.severity) ); - const unexpected = severe.filter((advisory) => !accepted.has(advisory.github_advisory_id)); + const unexpected = severe.filter( + (advisory) => + !accepted.has(advisory.github_advisory_id) && + !isTemporarilyAcceptedBracesAdvisory(advisory) + ); const critical = severe.filter((advisory) => advisory.severity === 'critical').length; const high = severe.filter((advisory) => advisory.severity === 'high').length; console.log( diff --git a/scripts/dependency-audit-policy.mjs b/scripts/dependency-audit-policy.mjs new file mode 100644 index 00000000..07c244e5 --- /dev/null +++ b/scripts/dependency-audit-policy.mjs @@ -0,0 +1,30 @@ +const temporaryBracesAdvisory = { + expiresAt: Date.UTC(2026, 9, 18), + id: 'GHSA-vfj7-8cjw-p6xm', + moduleName: 'braces', + patchedVersions: '<0.0.0', + path: '.>@vercel/node>ts-morph>@ts-morph/common>fast-glob>micromatch>braces', + version: '3.0.3', + vulnerableVersions: '<=3.0.3', +}; + +export function isTemporarilyAcceptedBracesAdvisory(advisory, now = new Date()) { + if (now.getTime() >= temporaryBracesAdvisory.expiresAt) return false; + if ( + advisory.github_advisory_id !== temporaryBracesAdvisory.id || + advisory.module_name !== temporaryBracesAdvisory.moduleName || + advisory.severity !== 'high' || + advisory.vulnerable_versions !== temporaryBracesAdvisory.vulnerableVersions || + advisory.patched_versions !== temporaryBracesAdvisory.patchedVersions || + advisory.findings?.length !== 1 + ) { + return false; + } + + const [finding] = advisory.findings; + return ( + finding.version === temporaryBracesAdvisory.version && + finding.paths?.length === 1 && + finding.paths[0] === temporaryBracesAdvisory.path + ); +} diff --git a/scripts/dependency-audit-policy.test.mjs b/scripts/dependency-audit-policy.test.mjs new file mode 100644 index 00000000..daec15d6 --- /dev/null +++ b/scripts/dependency-audit-policy.test.mjs @@ -0,0 +1,62 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { isTemporarilyAcceptedBracesAdvisory } from './dependency-audit-policy.mjs'; + +const knownFinding = { + github_advisory_id: 'GHSA-vfj7-8cjw-p6xm', + module_name: 'braces', + severity: 'high', + vulnerable_versions: '<=3.0.3', + patched_versions: '<0.0.0', + findings: [ + { + version: '3.0.3', + paths: ['.>@vercel/node>ts-morph>@ts-morph/common>fast-glob>micromatch>braces'], + }, + ], +}; + +test('temporarily accepts only the known braces advisory finding before expiry', () => { + assert.equal( + isTemporarilyAcceptedBracesAdvisory(knownFinding, new Date('2026-10-17T23:59:59Z')), + true + ); +}); + +test('rejects the advisory at expiry', () => { + assert.equal( + isTemporarilyAcceptedBracesAdvisory(knownFinding, new Date('2026-10-18T00:00:00Z')), + false + ); +}); + +test('rejects changed versions, paths, and additional findings', () => { + assert.equal( + isTemporarilyAcceptedBracesAdvisory( + { ...knownFinding, findings: [{ ...knownFinding.findings[0], version: '3.0.2' }] }, + new Date('2026-10-04T00:00:00Z') + ), + false + ); + assert.equal( + isTemporarilyAcceptedBracesAdvisory( + { ...knownFinding, findings: [{ ...knownFinding.findings[0], paths: ['.>unexpected>braces'] }] }, + new Date('2026-10-04T00:00:00Z') + ), + false + ); + assert.equal( + isTemporarilyAcceptedBracesAdvisory( + { ...knownFinding, findings: [...knownFinding.findings, ...knownFinding.findings] }, + new Date('2026-10-04T00:00:00Z') + ), + false + ); + assert.equal( + isTemporarilyAcceptedBracesAdvisory( + { ...knownFinding, github_advisory_id: 'GHSA-other' }, + new Date('2026-10-04T00:00:00Z') + ), + false + ); +}); From 22a6d9b5dae7c3187645b971d67413438b6659af Mon Sep 17 00:00:00 2001 From: Sarthak Agrawal Date: Sun, 4 Oct 2026 09:59:02 +0530 Subject: [PATCH 4/4] Match audit exception gate formatting --- scripts/check-code-health.mjs | 3 +-- scripts/dependency-audit-policy.test.mjs | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/check-code-health.mjs b/scripts/check-code-health.mjs index b78b3dc2..31c70507 100644 --- a/scripts/check-code-health.mjs +++ b/scripts/check-code-health.mjs @@ -240,8 +240,7 @@ function checkDependencies() { ); const unexpected = severe.filter( (advisory) => - !accepted.has(advisory.github_advisory_id) && - !isTemporarilyAcceptedBracesAdvisory(advisory) + !accepted.has(advisory.github_advisory_id) && !isTemporarilyAcceptedBracesAdvisory(advisory) ); const critical = severe.filter((advisory) => advisory.severity === 'critical').length; const high = severe.filter((advisory) => advisory.severity === 'high').length; diff --git a/scripts/dependency-audit-policy.test.mjs b/scripts/dependency-audit-policy.test.mjs index daec15d6..51bad119 100644 --- a/scripts/dependency-audit-policy.test.mjs +++ b/scripts/dependency-audit-policy.test.mjs @@ -40,7 +40,10 @@ test('rejects changed versions, paths, and additional findings', () => { ); assert.equal( isTemporarilyAcceptedBracesAdvisory( - { ...knownFinding, findings: [{ ...knownFinding.findings[0], paths: ['.>unexpected>braces'] }] }, + { + ...knownFinding, + findings: [{ ...knownFinding.findings[0], paths: ['.>unexpected>braces'] }], + }, new Date('2026-10-04T00:00:00Z') ), false