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.
3 issues found across 117 files
Confidence score: 2/5
- In
apps/browser-extension/security-questionnaire-ext/src/lib/queue.ts, the regenerate flow can overwrite manually edited non-emptyitem.answervalues, which risks losing user-entered content during normal use — preserve edited answers and only seed generated text for untouched/empty items. - In
apps/browser-extension/security-questionnaire-ext/src/lib/dom/sheets-dom.ts, matrix allocation appears to use absolute sheet indices instead of visible-range-normalized indices, which can create oversized arrays and slow parsing on larger sheets — normalize indices to visible bounds before allocation. - In
apps/browser-extension/security-questionnaire-ext/src/entrypoints/sidepanel/active-tab.ts(and duplicate logic inpopup/main.ts), duplicatedgetHostimplementations can drift and cause inconsistent host handling over time — import and reuse a single shared implementation.
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/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/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>
</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
| 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>
| 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>
- Use a closed shadow root for the domain confirmation dialog so the host page cannot reach its controls. - Escape leading formula characters when building Sheets paste TSV, so answers starting with "-" stay text instead of being evaluated. - Restrict the wildcard trusted-origin match to HTTPS; explicit localhost entries are unaffected. - Stop generate-all from overwriting approved or manually edited answers. - Hold the inline preview locally so concurrent generates cannot render a result into another field's preview. - Finalize queue item status when single generation fails, instead of leaving the item stuck in "generating". - Implement the Google Docs footer copy action, which had no handler, and label/enable the button to match. Adds coverage for the paste plan, batch candidate selection, docs footer, clipboard text, and the origin policy.
…view-fixes # Conflicts: # apps/api/src/auth/auth.server.ts # bun.lock
…release, add docs The release workflow required a Google OAuth client id and failed the build when the manifest had none, so the extension could not ship without direct Google Sheets API access. Treat it as optional: verify the manifest only when the secret is set, and drop it from the required-secret check. Adds the extension documentation page and links it from the docs nav.
Detection and data correctness: - Stop dropping questionnaire rows whose text looks like a column label. Any cell under 48 characters containing "control", "requirement" or "question" was discarded, which silently lost common questions such as "Are security controls documented?". - Ignore answer-style statements in the keyword fallback so filled-in answers are not re-detected as new questions. - Reject row/column 0 when parsing sheet targets; A1 notation is 1-based. - Reject negative sheet ids when building formatting requests. Concurrency and data loss: - Apply batch results to the stored queue rather than a snapshot, and skip items the user edited or approved while generation was in flight. - Serialize storage read-merge-writes so concurrent updates are not lost. - Keep the sheet mapping dialog populated when a save fails. - Surface autosave failures instead of dropping them. Robustness: - Guard JSON parsing in the API client, GViz table parsing and gid decoding. - Let one Sheets endpoint failure fall through to the next. - Fall back to local detection when the background message fails. - Add Vary headers to CORS responses, including rejected origins. - Trim organization ids and reject empty question ids and whitespace-only approvals. - Tighten isItemResponse to validate the queue it claims to carry. Cleanup: drop the unused ReviewPanel and the unhandled comp:generate-visible-page message, dedupe getHost and surface validation.
The replacement CORS middleware dropped HEAD, which app.enableCors allowed by default, so cross-origin HEAD preflights would have started failing.
There was a problem hiding this comment.
1 issue found across 30 files (changes from recent commits).
Confidence score: 3/5
- In
apps/browser-extension/security-questionnaire-ext/src/lib/background/batch-generation.ts,commitupdateswriteQueuewith chained promises but doesn’t recover from a rejectedloadQueue()/saveQueue(), so one transient failure can poison the queue and block all later writes; this can stall batch generation and leave data unsaved—wrap each queued step with error handling/recovery so the chain continues after failures.
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/lib/background/batch-generation.ts">
<violation number="1" location="apps/browser-extension/security-questionnaire-ext/src/lib/background/batch-generation.ts:50">
P1: The `commit` function chains writes via `writeQueue = writeQueue.then(...)` but does not isolate failures. If a single `loadQueue()` or `saveQueue()` call rejects, the entire `writeQueue` promise chain becomes permanently rejected, causing all subsequent in-flight commits to silently skip their work — the generated answers are computed but never persisted. This is a data persistence gap during concurrent batch generation.
Unlike the similar `serializeWrite` in `storage.ts` (which uses `result.catch(() => undefined)` to keep the chain alive), the `commit` function has no error isolation. The `runConcurrent` worker pool runs with shared concurrency, so a failure in one worker can leave other in-flight workers' answers unsaved without any indication to the caller (the handler is simply never invoked on a rejected chain).
**Recommendation**: Add error isolation similar to `serializeWrite` so a single commit failure does not silently disable all subsequent commits.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
| // Each result is applied to the freshly stored queue inside a serialized | ||
| // section, so a long-running request cannot write back a stale snapshot. | ||
| let writeQueue: Promise<void> = Promise.resolve(); | ||
| const commit = ( |
There was a problem hiding this comment.
P1: The commit function chains writes via writeQueue = writeQueue.then(...) but does not isolate failures. If a single loadQueue() or saveQueue() call rejects, the entire writeQueue promise chain becomes permanently rejected, causing all subsequent in-flight commits to silently skip their work — the generated answers are computed but never persisted. This is a data persistence gap during concurrent batch generation.
Unlike the similar serializeWrite in storage.ts (which uses result.catch(() => undefined) to keep the chain alive), the commit function has no error isolation. The runConcurrent worker pool runs with shared concurrency, so a failure in one worker can leave other in-flight workers' answers unsaved without any indication to the caller (the handler is simply never invoked on a rejected chain).
Recommendation: Add error isolation similar to serializeWrite so a single commit failure does not silently disable all subsequent commits.
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/background/batch-generation.ts, line 50:
<comment>The `commit` function chains writes via `writeQueue = writeQueue.then(...)` but does not isolate failures. If a single `loadQueue()` or `saveQueue()` call rejects, the entire `writeQueue` promise chain becomes permanently rejected, causing all subsequent in-flight commits to silently skip their work — the generated answers are computed but never persisted. This is a data persistence gap during concurrent batch generation.
Unlike the similar `serializeWrite` in `storage.ts` (which uses `result.catch(() => undefined)` to keep the chain alive), the `commit` function has no error isolation. The `runConcurrent` worker pool runs with shared concurrency, so a failure in one worker can leave other in-flight workers' answers unsaved without any indication to the caller (the handler is simply never invoked on a rejected chain).
**Recommendation**: Add error isolation similar to `serializeWrite` so a single commit failure does not silently disable all subsequent commits.</comment>
<file context>
@@ -41,8 +42,22 @@ export async function generateQueueItemsInBatches(params: {
+ // Each result is applied to the freshly stored queue inside a serialized
+ // section, so a long-running request cannot write back a stale snapshot.
+ let writeQueue: Promise<void> = Promise.resolve();
+ const commit = (
+ apply: (current: TabQuestionQueue) => TabQuestionQueue,
+ ): Promise<void> => {
</file context>
… the working API Switches Chrome Web Store auth from a personal refresh token to a service account JWT, which does not expire and is not tied to an individual account. Verified against the live API: the token exchange succeeds and the service account can read the store item. Every v2 endpoint returns 404 for this publisher while v1.1 works with the same token, so the upload, status and publish calls now use v1.1. That API is supported until 2026-10-15. Adds a publish_target choice so a manual run can ship to trusted testers before going public.
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 draft and insert security questionnaire answers on vendor pages and Google Forms/Docs/Sheets, and replaces CORS handling with a stricter origin policy and route-scoped extension checks. Google Sheets OAuth is optional so the extension can ship without direct Sheets API access.
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 via direct Sheets API (auto width/wrap) or guided paste; Google Docs adds a footer copy action.origin-policy(HTTPS-only wildcard trust, Chrome extension route allowlist) and addedcors-origin.middlewarewith preflight/credentials, Vary headers, and HEAD in allowed methods;/v1/questionnaire/answer-singleaccepts webpage-only drafts and enforces org-scoped saves with improved Swagger.release; publish via service account JWT using Chrome Web Store v1.1 endpoints, with apublish_targetselector;.wxt/distignored; Google Sheets OAuth client ID optional and manifest check gated; added extension docs and nav link.Bug Fixes
Written for commit 717bc8a. Summary will update on new commits.