Skip to content

feat(security-questionnaire): add browser extension - #3064

Merged
tofikwest merged 16 commits into
mainfrom
feat/security-questionnaire-extension
Jul 28, 2026
Merged

feat(security-questionnaire): add browser extension#3064
tofikwest merged 16 commits into
mainfrom
feat/security-questionnaire-extension

Conversation

@tofikwest

@tofikwest tofikwest commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

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:

  • Allows webpage-only /v1/questionnaire/answer-single generation without persisting questionnaire records.
  • Adds COMP_EXTENSION_TRUSTED_ORIGINS and method/path-scoped extension CORS/origin handling.

Validation:

  • bun run --filter '@trycompai/security-questionnaire-extension' test
  • bun run --filter '@trycompai/security-questionnaire-extension' typecheck
  • bun run --filter '@trycompai/security-questionnaire-extension' build
  • cd apps/api && bunx jest src/auth/origin-check.middleware.spec.ts src/auth/cors-origin.middleware.spec.ts --passWithNoTests

Note:

  • Full API typecheck currently fails in unrelated existing cloud-security/integration-platform spec files, not in this PR's auth/extension files.

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

    • Extension: @trycompai/security-questionnaire-extension with 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.
    • API: origin policy with HTTPS-only wildcard trust and Chrome extension route allowlist; cors-origin.middleware with preflight/credentials, Vary headers, and HEAD allowed; CORS error filter aligned; /v1/questionnaire/answer-single supports webpage-only drafts and org-scoped saves with clearer Swagger.
    • Release/Docs: GitHub Action to build/sign/publish from release, publishing via service account JWT to Chrome Web Store v1.1 with a publish_target; .wxt/dist ignored; optional Sheets OAuth manifest checks gated; refuse publish if manifest points at localhost; added extension docs and nav.
  • Bug Fixes

    • Detection: keep short questions containing “control/requirement/question”; ignore answer-style statements; reject invalid sheet targets (row/col 0) and negative sheet IDs.
    • Concurrency/data loss: apply batch results to stored queue and skip items edited/approved in-flight; serialize storage reads/merges; isolate failed queue writes in batch generation; restore prior status after failed writes while keeping items retryable; surface autosave failures.
    • Robustness: guard JSON and GViz parsing and gid decoding; fall back across Sheets endpoints and to local detection when background messages fail; add Vary on all CORS paths and keep HEAD allowed; trim organization IDs; reject empty question IDs or whitespace-only approvals; tighten response validation.
    • UX/polish: escape leading formula chars in Sheets TSV; prevent generate-all from overwriting approved/edited answers; scope inline previews per field; finalize item status after single-generate failures; implement Docs footer copy; fix release manifest version check; add tests for paste plans, batch selection/failure handling, Docs footer/clipboard, and origin policy; drop unused activeTab permission to pass store review.

Written for commit c605961. Summary will update on new commits.

Review in cubic

@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jul 28, 2026 6:18pm
comp-framework-editor Ready Ready Preview, Comment Jul 28, 2026 6:18pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped Jul 28, 2026 6:18pm

Request Review

@mintlify

mintlify Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
CompAI 🟢 Ready View Preview Jun 8, 2026, 11:18 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-empty item.answer values, 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 in popup/main.ts), duplicated getHost implementations 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,

@cubic-dev-ai cubic-dev-ai Bot Jun 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Comment thread apps/browser-extension/security-questionnaire-ext/src/entrypoints/content.ts Outdated
Comment thread apps/browser-extension/security-questionnaire-ext/src/lib/sheets-paste-plan.ts Outdated
Comment thread apps/api/src/auth/origin-policy.ts
Comment thread apps/browser-extension/security-questionnaire-ext/src/lib/scan-debug.ts Outdated
Comment thread apps/browser-extension/security-questionnaire-ext/src/lib/dom/review-panel.ts Outdated
return tab;
}

export function getHost(url: string): string {

@cubic-dev-ai cubic-dev-ai Bot Jun 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

- 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.
@vercel
vercel Bot temporarily deployed to Preview – portal July 27, 2026 18:18 Inactive
…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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@vercel
vercel Bot temporarily deployed to Preview – portal July 28, 2026 18:14 Inactive
@tofikwest
tofikwest merged commit e678421 into main Jul 28, 2026
15 checks passed
@tofikwest
tofikwest deleted the feat/security-questionnaire-extension branch July 28, 2026 18:33
tofikwest added a commit that referenced this pull request Jul 28, 2026
…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.
claudfuen pushed a commit that referenced this pull request Jul 29, 2026
# [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))
@claudfuen

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.111.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants