feat(security-questionnaire): add browser extension - #3064
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
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.
…ission Every activeTab occurrence in the source is a local variable name; the permission itself is never relied on, and the <all_urls> host permission already covers the same access. The store rejects versions that request permissions they do not need. Also refuses to publish a package whose manifest still points at localhost, which is what the build produces when the public URL env vars are missing.
When a queue write failed the question stayed marked `generating`, and that status is excluded from batch candidates — so every later Generate All skipped it and the answer could never be recovered. Reset the item to pending after a failed write.
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
…ailed write Resetting a failed question to pending discarded whatever state it had before the batch, so a transient storage error downgraded a question that already held a generated or flagged answer. Restore the status captured before the batch marked it generating.
…ins (#3523) * test(api): lock in that production origins still pass CORS The CORS layer was replaced in #3064 to support the questionnaire extension. These assert the four origin classes that actually reach production — the app, the portal, an arbitrary trycomp.ai subdomain, and a customer custom domain — still receive the right headers, that an untrusted origin is still rejected, and that resolution does not depend on AUTH_TRUSTED_ORIGINS, which the API is not given in production. Tests only; no production code changes. * test(api): use reserved example domains in the auth specs The specs hardcoded a developer's real local unpacked-extension id and used evil.com / acmecorp.com, both of which are real registrable domains, in a public repository. Replace them with a synthetic extension id and RFC 2606 reserved names (untrusted.example, trust.example.com), and drop the attack framing. Behaviour of the tests is unchanged.
# [3.111.0](v3.110.1...v3.111.0) (2026-07-29) ### Bug Fixes * **api:** classify the take-over method on an unclear outcome too ([539fdba](539fdba)) * **api:** only classify a switchable passkey when a code method exists ([#3528](#3528)) ([0d79093](0d79093)) * **app:** make a half-finished connect resumable, not a forced full-screen step ([#3525](#3525)) ([b1afcae](b1afcae)) * **cloud-security:** add missing logGroupName to CreateLogGroup remediation ([#3515](#3515)) ([76ae56c](76ae56c)) * harden sign-in classification + align take-over messaging ([#3527](#3527)) ([914cb1e](914cb1e)) * **integrations:** wrap aws add account form in dialog and scroll into view ([#3526](#3526)) ([07e91ae](07e91ae)), closes [#418](#418) * make the 2FA take-over universal — tailor guidance to what the page asks for ([#3520](#3520)) ([b12f3fe](b12f3fe)) * **policies:** remove archived policies from framework controls after unlinking ([#3513](#3513)) ([d639df2](d639df2)) * **training:** defer completion email and share canonical training IDs ([#3529](#3529)) ([fecb556](fecb556)) * **training:** remove rbac gate from mark-complete endpoint ([#3501](#3501)) ([8c5e98f](8c5e98f)), closes [#3455](#3455) ### Features * connection longevity + one-click 'Make permanent' 2FA ([#3524](#3524)) ([7345e3c](7345e3c)) * **policies:** add bulk upload for policy migration ([#3514](#3514)) ([1940f06](1940f06)) * **security-questionnaire:** add browser extension ([#3064](#3064)) ([e678421](e678421))
|
🎉 This PR is included in version 3.111.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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 hardens API CORS/origin checks with HTTPS-only wildcards, route-scoped extension access, and correct HEAD handling. Google Sheets OAuth is optional so we can ship without direct Sheets API access.
New Features
@trycompai/security-questionnaire-extensionwith popup and side panel, inline buttons/preview, org switching, closed-Shadow DOM domain confirmation, auto content script injection, and a review/approve/insert queue. Google Sheets via direct Sheets API (auto width/wrap) or guided paste with TSV escaping; Google Docs adds a footer copy action.cors-origin.middlewarewith preflight/credentials, Vary headers, and HEAD allowed; CORS error filter aligned;/v1/questionnaire/answer-singlesupports webpage-only drafts and org-scoped saves with clearer Swagger.release, publishing via service account JWT to Chrome Web Store v1.1 with apublish_target;.wxt/distignored; optional Sheets OAuth manifest checks gated; refuse publish if manifest points at localhost; added extension docs and nav.Bug Fixes
Written for commit c605961. Summary will update on new commits.