feat(security-questionnaire): add browser extension#3064
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
There was a problem hiding this comment.
32 issues found across 117 files
Confidence score: 2/5
- Several high-confidence, user-facing regressions are present in the extension flow, including disabled docs footer actions in
apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/surface-ui.tsand regenerate overwriting manual edits inapps/browser-extension/security-questionnaire-ext/src/lib/queue.ts. - There are concrete correctness/race risks in async UI handling (
apps/browser-extension/security-questionnaire-ext/src/entrypoints/content.ts) and fallback reliability gaps (apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-detection.ts,apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-runtime.ts) that can misroute previews or stop detection. - Security posture needs tightening before merge: formula-injection hardening is missing in
apps/browser-extension/security-questionnaire-ext/src/lib/sheets-paste-plan.ts, origin/CORS handling has unsafe edge cases inapps/api/src/auth/origin-policy.tsandapps/api/src/auth/cors-origin.middleware.ts, and dialog confirmation can be bypassed via open shadow root inapps/browser-extension/security-questionnaire-ext/src/lib/dom/content-dialog.ts. - Pay close attention to
apps/browser-extension/security-questionnaire-ext/src/lib/sheets-paste-plan.ts,apps/api/src/auth/origin-policy.ts,apps/api/src/auth/cors-origin.middleware.ts,apps/browser-extension/security-questionnaire-ext/src/entrypoints/content.ts,apps/browser-extension/security-questionnaire-ext/src/lib/queue.ts- these combine security-sensitive behavior with high-confidence user-impacting regressions.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/surface-ui.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/surface-ui.ts:32">
P2: Docs CTA label says “Insert” while action is copy, creating misleading UI behavior.</violation>
<violation number="2" location="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/surface-ui.ts:45">
P1: Docs footer action is always disabled, so `copy-sheet-answers` cannot run.</violation>
</file>
<file name="apps/api/src/auth/cors-origin.middleware.ts">
<violation number="1" location="apps/api/src/auth/cors-origin.middleware.ts:23">
P2: Dynamic CORS response headers are missing `Vary`, which can cause cache-mixed CORS decisions across origins. Add `Vary: Origin` (and request-header vary) when reflecting origin/headers.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-detection.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-detection.ts:94">
P2: Uncaught fetch errors break endpoint fallback flow. Catch fetch failures in `fetchTable` and return `null` so later endpoints can still be attempted.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-runtime.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-runtime.ts:10">
P2: Runtime message failure prevents local sheet-question fallback detection. Catch message errors here so page/DOM detection still runs.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/background/google-sheets-formatting.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/background/google-sheets-formatting.ts:53">
P2: `gid` validation allows negative sheet IDs, which can generate invalid Google Sheets batchUpdate ranges.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-table.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-table.ts:27">
P2: Uncaught JSON.parse can crash table parsing on malformed GViz responses. Guard parse errors and return null for invalid payloads.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/queue-approval.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/queue-approval.ts:6">
P2: Bulk approval accepts whitespace-only answers as approved content. Use a trimmed non-empty check before approving.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/storage.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/storage.ts:16">
P2: `setSelectedOrganizationId` does not trim/normalize input, allowing whitespace-only IDs to be persisted as active organization IDs.
(Based on your team's feedback about normalizing blank organization identifiers before persistence.) [FEEDBACK_USED].</violation>
<violation number="2" location="apps/browser-extension/security-questionnaire-ext/src/lib/storage.ts:28">
P2: Non-atomic read/merge/write on shared storage maps can lose updates under concurrent calls. This can drop confirmed-domain or detection settings set by another tab/context.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/scan-debug.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/scan-debug.ts:8">
P3: Surface validation is duplicated instead of using the shared guard. Reuse `isQuestionnaireSurface` to keep one source of truth and avoid future mismatch bugs.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/sheet-mapping-actions.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/sheet-mapping-actions.ts:40">
P2: Failed mapping save drops the user’s submitted draft instead of allowing retry with existing input. Keep the dialog open or restore the draft after error so users don’t lose entered mapping data.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/dom/review-panel.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/review-panel.ts:5">
P3: `ReviewPanel` is unused dead code; no caller imports or instantiates it.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/answer-edits.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/answer-edits.ts:10">
P2: Autosave errors are silently ignored in the change handler, so failed saves can be lost without user-visible feedback.
(Based on your team's feedback about surfacing failed autosaves instead of silently dropping them.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/background/queue-actions.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/background/queue-actions.ts:125">
P2: Single-item generation can leave queue items permanently in `generating` when API generation fails. Catch generation errors and convert them to a failed/flagged generated-answer payload so status is finalized.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/api.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/api.ts:49">
P2: Uncaught JSON.parse can mask HTTP failures and throw non-actionable SyntaxError. Parse failures should fall back to a normal request error path.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/queue.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/queue.ts:137">
P1: Regenerate path clobbers manually edited answers. Preserve edited non-empty item.answer and skip seeding generated text for those items.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-dom.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-dom.ts:27">
P2: Matrix allocation uses absolute sheet indices instead of normalizing to visible bounds, causing unnecessary large arrays and slow parsing.</violation>
<violation number="2" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-dom.ts:84">
P2: `parseGid` can throw on malformed URL hash because `decodeURIComponent` is not guarded.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/entrypoints/content.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/entrypoints/content.ts:131">
P1: Using global `activePreview` across awaited generate calls causes cross-request UI races. Concurrent clicks can render one field’s result in another field’s preview or close the wrong preview.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/content-messaging.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/content-messaging.ts:24">
P2: `comp:generate-visible-page` is parsed but never handled by the content script. Calls of this message will always return an error despite being treated as valid input.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/background/batch-generation.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/background/batch-generation.ts:45">
P2: Batch generation writes stale queue snapshots and can clobber concurrent queue updates. This can revert item approvals/edits while generation is in progress.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/sheets-paste-plan.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/sheets-paste-plan.ts:62">
P2: Row/column parsing accepts 0, but Sheets A1 coordinates are 1-based. Reject zero indices in fieldId parsing to avoid invalid range output.</violation>
<violation number="2" location="apps/browser-extension/security-questionnaire-ext/src/lib/sheets-paste-plan.ts:95">
P1: TSV cell escaping misses formula-injection hardening for leading spreadsheet formula characters. Prefix dangerous leading chars after quote-escaping to prevent pasted cells executing formulas.
(Based on your team's feedback about CSV/TSV formula-injection escaping order.) [FEEDBACK_USED].</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/content-collector.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/content-collector.ts:21">
P2: Nullish coalescing here suppresses fallback debug text when `sheetScan.message` is empty. Use a truthy fallback so content-scan debug is shown instead of blank status.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/active-tab.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/active-tab.ts:9">
P3: Duplicate `getHost` implementation in popup/main.ts — import from active-tab.ts instead.</violation>
</file>
<file name="apps/api/src/auth/origin-policy.ts">
<violation number="1" location="apps/api/src/auth/origin-policy.ts:78">
P1: Wildcard trusted-origin fallback accepts non-HTTPS origins. Require HTTPS before allowing `.trycomp.ai`/`.trust.inc` hostname matches.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/response-guards.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/response-guards.ts:24">
P2: `isItemResponse` claims `queue` is present but does not validate it. This makes the type guard unsound and can let malformed runtime messages pass as valid.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/message-utils.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/message-utils.ts:10">
P2: `parseDetectedQuestion` allows empty-string `id`, which can cause incorrect matches in the queue lookup (`fieldId === id`). Add `value.id.length === 0` to the rejection condition.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/dom/field-detection.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/field-detection.ts:275">
P2: Keyword fallback misclassifies first-person compliance statements as questions, causing over-detection of fields from answer-like text.
(Based on your team's feedback about conservative questionnaire chunking and excluding first-person prefixes.) [FEEDBACK_USED].</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-question-cells.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-question-cells.ts:200">
P2: Header detection uses substring matching for answer headers, so data rows containing words like "response" can be misclassified as headers and removed from extraction.</violation>
</file>
<file name="apps/browser-extension/security-questionnaire-ext/src/lib/dom/content-dialog.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/dom/content-dialog.ts:10">
P1: Domain confirmation can be bypassed because the dialog shadow root is open to page scripts. Use a closed shadow root so untrusted page JS cannot programmatically press Confirm.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Partial review: This PR has more than 100 files, so cubic reviewed the highest-priority files first.
For a deeper review of large PRs, comment @cubic-dev-ai ultrareview. Learn more.
Re-trigger cubic
| answerCount: number; | ||
| surface: Surface; | ||
| }): string { | ||
| if (params.surface === 'docs') return 'disabled'; |
There was a problem hiding this comment.
P1: Docs footer action is always disabled, so copy-sheet-answers cannot run.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/surface-ui.ts, line 45:
<comment>Docs footer action is always disabled, so `copy-sheet-answers` cannot run.</comment>
<file context>
@@ -0,0 +1,47 @@
+ answerCount: number;
+ surface: Surface;
+}): string {
+ if (params.surface === 'docs') return 'disabled';
+ return params.approved === 0 ? 'disabled' : '';
+}
</file context>
| return { | ||
| ...item, | ||
| status: hasAnswer ? 'generated' : 'flagged', | ||
| answer: hasAnswer ? answer : null, |
There was a problem hiding this comment.
P1: Regenerate path clobbers manually edited answers. Preserve edited non-empty item.answer and skip seeding generated text for those items.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/lib/queue.ts, line 137:
<comment>Regenerate path clobbers manually edited answers. Preserve edited non-empty item.answer and skip seeding generated text for those items.</comment>
<file context>
@@ -0,0 +1,272 @@
+ return {
+ ...item,
+ status: hasAnswer ? 'generated' : 'flagged',
+ answer: hasAnswer ? answer : null,
+ confidence: hasAnswer ? getAnswerConfidence(params.answer) : 'low',
+ sources: params.answer.sources,
</file context>
|
|
||
| setInlineButtonState(button, 'busy'); | ||
| activePreview?.close(); | ||
| activePreview = new InlinePreview(button); |
There was a problem hiding this comment.
P1: Using global activePreview across awaited generate calls causes cross-request UI races. Concurrent clicks can render one field’s result in another field’s preview or close the wrong preview.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/entrypoints/content.ts, line 131:
<comment>Using global `activePreview` across awaited generate calls causes cross-request UI races. Concurrent clicks can render one field’s result in another field’s preview or close the wrong preview.</comment>
<file context>
@@ -0,0 +1,296 @@
+
+ setInlineButtonState(button, 'busy');
+ activePreview?.close();
+ activePreview = new InlinePreview(button);
+ activePreview.showLoading(candidate.question);
+
</file context>
| } | ||
|
|
||
| function escapeTsvCell(value: string): string { | ||
| return value.replace(/\r?\n/g, ' ').replace(/\t/g, ' ').trim(); |
There was a problem hiding this comment.
P1: TSV cell escaping misses formula-injection hardening for leading spreadsheet formula characters. Prefix dangerous leading chars after quote-escaping to prevent pasted cells executing formulas.
(Based on your team's feedback about CSV/TSV formula-injection escaping order.) .
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/lib/sheets-paste-plan.ts, line 95:
<comment>TSV cell escaping misses formula-injection hardening for leading spreadsheet formula characters. Prefix dangerous leading chars after quote-escaping to prevent pasted cells executing formulas.
(Based on your team's feedback about CSV/TSV formula-injection escaping order.) .</comment>
<file context>
@@ -0,0 +1,96 @@
+}
+
+function escapeTsvCell(value: string): string {
+ return value.replace(/\r?\n/g, ' ').replace(/\t/g, ' ').trim();
+}
</file context>
| origin: string; | ||
| path: string; | ||
| }): boolean { | ||
| return ( |
There was a problem hiding this comment.
P1: Wildcard trusted-origin fallback accepts non-HTTPS origins. Require HTTPS before allowing .trycomp.ai/.trust.inc hostname matches.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/auth/origin-policy.ts, line 78:
<comment>Wildcard trusted-origin fallback accepts non-HTTPS origins. Require HTTPS before allowing `.trycomp.ai`/`.trust.inc` hostname matches.</comment>
<file context>
@@ -0,0 +1,112 @@
+ origin: string;
+ path: string;
+}): boolean {
+ return (
+ isCompExtensionOrigin(params.origin) &&
+ isCompExtensionAllowedRoute({ method: params.method, path: params.path })
</file context>
| ) { | ||
| return true; | ||
| } | ||
| return /\b(access|audit|availability|backup|business continuity|compliance|control|data|disaster recovery|encryption|incident|information security|mfa|password|policy|privacy|risk|soc 2|security|subprocessor|vendor)\b/i |
There was a problem hiding this comment.
P2: Keyword fallback misclassifies first-person compliance statements as questions, causing over-detection of fields from answer-like text.
(Based on your team's feedback about conservative questionnaire chunking and excluding first-person prefixes.) .
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/lib/dom/field-detection.ts, line 275:
<comment>Keyword fallback misclassifies first-person compliance statements as questions, causing over-detection of fields from answer-like text.
(Based on your team's feedback about conservative questionnaire chunking and excluding first-person prefixes.) .</comment>
<file context>
@@ -0,0 +1,277 @@
+ ) {
+ return true;
+ }
+ return /\b(access|audit|availability|backup|business continuity|compliance|control|data|disaster recovery|encryption|incident|information security|mfa|password|policy|privacy|risk|soc 2|security|subprocessor|vendor)\b/i
+ .test(value);
+}
</file context>
| questionColumn: number; | ||
| }): number { | ||
| const answerIndex = params.header.findIndex((value) => | ||
| /answer|response|reply|vendor response/i.test(value), |
There was a problem hiding this comment.
P2: Header detection uses substring matching for answer headers, so data rows containing words like "response" can be misclassified as headers and removed from extraction.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-question-cells.ts, line 200:
<comment>Header detection uses substring matching for answer headers, so data rows containing words like "response" can be misclassified as headers and removed from extraction.</comment>
<file context>
@@ -0,0 +1,287 @@
+ questionColumn: number;
+}): number {
+ const answerIndex = params.header.findIndex((value) =>
+ /answer|response|reply|vendor response/i.test(value),
+ );
+ return answerIndex >= 0 ? answerIndex : params.questionColumn + 1;
</file context>
| if (!isRecord(value) || !isRecord(value.debug)) return null; | ||
| const debug = value.debug; | ||
| if ( | ||
| debug.surface !== 'sheets' && |
There was a problem hiding this comment.
P3: Surface validation is duplicated instead of using the shared guard. Reuse isQuestionnaireSurface to keep one source of truth and avoid future mismatch bugs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/lib/scan-debug.ts, line 8:
<comment>Surface validation is duplicated instead of using the shared guard. Reuse `isQuestionnaireSurface` to keep one source of truth and avoid future mismatch bugs.</comment>
<file context>
@@ -0,0 +1,66 @@
+ if (!isRecord(value) || !isRecord(value.debug)) return null;
+ const debug = value.debug;
+ if (
+ debug.surface !== 'sheets' &&
+ debug.surface !== 'docs' &&
+ debug.surface !== 'forms' &&
</file context>
| import type { WritableField } from './field-detection'; | ||
| import { insertAnswerIntoField } from './field-actions'; | ||
|
|
||
| export class ReviewPanel { |
There was a problem hiding this comment.
P3: ReviewPanel is unused dead code; no caller imports or instantiates it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/lib/dom/review-panel.ts, line 5:
<comment>`ReviewPanel` is unused dead code; no caller imports or instantiates it.</comment>
<file context>
@@ -0,0 +1,142 @@
+import type { WritableField } from './field-detection';
+import { insertAnswerIntoField } from './field-actions';
+
+export class ReviewPanel {
+ private root: HTMLElement;
+
</file context>
| return tab; | ||
| } | ||
|
|
||
| export function getHost(url: string): string { |
There was a problem hiding this comment.
P3: Duplicate getHost implementation in popup/main.ts — import from active-tab.ts instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/active-tab.ts, line 9:
<comment>Duplicate `getHost` implementation in popup/main.ts — import from active-tab.ts instead.</comment>
<file context>
@@ -0,0 +1,15 @@
+ return tab;
+}
+
+export function getHost(url: string): string {
+ try {
+ return new URL(url).host;
</file context>
Adds the Comp AI Security Questionnaire Chrome extension under apps/browser-extension/security-questionnaire-ext, including popup/sidepanel/content scripts, org switching, Google Forms and Sheets support, and Chrome Web Store release workflow.
API changes:
Validation:
Note:
Summary by cubic
Adds a Chrome MV3 extension to generate and insert security questionnaire answers on vendor pages and Google Forms/Sheets, and replaces CORS handling with stricter, route-scoped origin checks for the extension. Also supports webpage-only single-answer generation and adds a release workflow.
New Features
@trycompai/security-questionnaire-extensionwith popup and side panel, inline buttons/preview, org switching, domain confirmation, auto content-script injection, and a review/approve/insert queue. Google Sheets support via direct Sheets API (auto column width/wrap) or guided paste.origin-policy(static vs Chrome extension checks) and addedcors-origin.middlewarefor preflight/credentials. Route-allowlist for the extension (/api/auth/get-session,/v1/auth/me,/api/auth/organization/set-active,/v1/questionnaire/answer-single)./v1/questionnaire/answer-singleaccepts webpage-only drafts and enforces org-scoped saves; improved Swagger docs.releasebranch; ignore.wxt/distin.gitignore.Migration
COMP_EXTENSION_TRUSTED_ORIGINS=chrome-extension://<extension-id>; keepAUTH_TRUSTED_ORIGINSfor web apps.WXT_PUBLIC_API_BASE_URLandWXT_PUBLIC_APP_BASE_URL; optionalWXT_EXTENSION_KEY(stable local ID) andWXT_GOOGLE_OAUTH_CLIENT_ID(direct Sheets API).releasebranch to trigger thesecurity-questionnaire-extension-releaseworkflow.Written for commit 4a11867. Summary will update on new commits.