Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@ to test; `email`, `types`, and `vite-plugins` are consumed from source, so they
`apps/storybook` has no unit tests by policy (UI is covered by Playwright). Keep new packages on this
matrix or document the exception here.

### Shared dependency versions (pnpm catalog)

Every dependency used by **two or more** workspaces is pinned once in the `catalog:` block of
`pnpm-workspace.yaml`, and each `package.json` references it as `"catalog:"` instead of a version:

```json
"devDependencies": { "typescript": "catalog:", "vitest": "catalog:" }
```

So bumping a shared dependency means editing the catalog entry — never a `package.json`. That is the
whole point: `nodeLinker: hoisted` hides a version split until it breaks, so `apps/web` was typing a
redis 5 client with redis 4's `RedisClientType` and one package was building on a different Vite major
than the other fourteen. Deps with a single consumer deliberately stay in their own `package.json`.

`pnpm lint` runs `scripts/check-catalog.mjs`, which fails if a workspace declares a literal version for
a catalogued name, or if a dependency is declared by 2+ workspaces without being catalogued. A peer
dependency *range* is exempt — it is a compatibility declaration for consumers, not an install pin, so
it may legitimately be looser than the catalog (`packages/survey-ui` declares react `^19.0.0` while
pinning 19.2.6 to build against). Adding a new package needs no wiring: the check resolves the
workspace globs from `pnpm-workspace.yaml` itself.

### Survey Packages Build & Cache

The `@formbricks/surveys` package is pre-compiled (Vite → UMD + ESM) and the built bundle is copied to `apps/web/public/js/`. The Next.js app imports from `dist/`, **not** the source files. This means:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ Let's have a chat about your survey needs and get you started.

## 🔒 Security

We take security very seriously. If you come across any security vulnerabilities, please disclose them by sending an email to security@formbricks.com. We appreciate your help in making our platform as secure as possible and are committed to working with you to resolve any issues quickly and efficiently. See [`SECURITY.md`](./SECURITY.md) for more information.
We take security very seriously. If you come across any security vulnerabilities, please disclose them by sending an email to security@formbricks.com. We appreciate your help in making our platform as secure as possible and are committed to working with you to resolve any issues quickly and efficiently. Please note that we do not offer bug bounties or any other payment for security reports, but we are happy to credit you in the release notes for the fix on request. See [`SECURITY.md`](./SECURITY.md) for more information.

<a id="license"></a>

Expand Down
25 changes: 20 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Security Policy of Formbricks

This is Formbrick's security policy. Please reach out to us
on Github or, if privately, via <security@formbricks.com>
This is the Formbricks security policy. Please report vulnerabilities
privately via <security@formbricks.com> rather than in public.

## Introduction

Expand All @@ -27,6 +27,8 @@ To understand and bolster our security stature, Formbricks undertakes:

Please do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties.

> **Formbricks does not offer bug bounties.** We do not pay for vulnerability reports of any kind. Public credit for your finding is available on request — see [D. Bug Bounties and Credit](#d-bug-bounties-and-credit).

### **A. When to Report a Vulnerability**

We invite you to report if:
Expand All @@ -51,7 +53,8 @@ In the interest of responsibly managing vulnerabilities, please adhere to the fo
> Do not reveal the problem to others until it has been resolved.

1. **Send a Detailed Report**:
- Raise a security report on [Github](https://github.com/formbricks/formbricks/issues/new/choose) or send an email to [security@formbricks.com](mailto:security@formbricks.com).
- Send an email to [security@formbricks.com](mailto:security@formbricks.com).
- Please do not open a GitHub issue for a vulnerability. The issue tracker is public, so filing there discloses the problem before a fix exists — which is what the line above asks you to avoid.
- Include:
- Problem description.
- Detailed, reproducible steps, with screenshots where possible.
Expand All @@ -65,11 +68,23 @@ In the interest of responsibly managing vulnerabilities, please adhere to the fo
- A project maintainer may engage with you for additional details or clarification.
- We appreciate your patience as we explore the reported item, verify its authenticity, and ascertain the existence of a vulnerability.

### **D. Bug Bounties and Credit**

Formbricks does not run a bug bounty program, and we want to be upfront about that before you invest your time:

- We do not pay bounties, rewards, gift cards, or goodwill payments for security reports. There are no exceptions, and this is not decided case by case.
- We have offered both a bounty and one-off payments in the past. The result was a sharp increase in low-quality and automated reports rather than better ones, so we stopped. It is a settled policy rather than a question of budget.
- Please do not attach an invoice, a payment request, or a payment condition to a report. We will still read and act on the report, but the answer on payment will be no.

What we do offer is **credit**. If you would like to be named, tell us in your report or at any point before the fix ships, and we will mention you in the release notes for the fix.

None of this changes how seriously we treat your report. A well-written vulnerability report is real work, and we are genuinely grateful for it — we simply pay it back in credit and a fast fix rather than in money.

---

### Please Read the below carefully

If you have followed the instructions above, we will **not** take any legal action against you in regard to the report,
We will handle your report with strict confidentiality, and not pass on your personal details to third parties without your permission, We will keep you informed of the progress towards resolving the problem, In the public information concerning the problem reported, we will give your name as the discoverer of the problem (unless you desire otherwise).
If you have followed the instructions above, we will **not** take any legal action against you in regard to the report.
We will handle your report with strict confidentiality and will not pass on your personal details to third parties without your permission. We will keep you informed of the progress towards resolving the problem. In the public information concerning the problem reported, we will name you as the discoverer of the problem only if you have asked to be credited. Otherwise, we will not publish your identity.

We, at Formbricks, wish to express our gratitude towards all individuals who assist us in fortifying our security posture. Your responsible disclosure and cooperation enable us to elevate our security protocols, safeguarding our platform and data therein.
20 changes: 10 additions & 10 deletions apps/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@
},
"devDependencies": {
"@chromatic-com/storybook": "5.0.2",
"@eslint/js": "9.39.5",
"@eslint/js": "catalog:",
"@formbricks/config-typescript": "workspace:*",
"@storybook/addon-a11y": "10.3.6",
"@storybook/addon-docs": "10.3.6",
"@storybook/addon-links": "10.3.6",
"@storybook/addon-onboarding": "10.3.6",
"@storybook/react-vite": "10.3.6",
"@tailwindcss/vite": "4.2.4",
"@vitejs/plugin-react": "5.1.4",
"eslint-plugin-react-hooks": "7.1.1",
"@storybook/react-vite": "catalog:",
"@tailwindcss/vite": "catalog:",
"@vitejs/plugin-react": "catalog:",
"eslint-plugin-react-hooks": "catalog:",
"eslint-plugin-react-refresh": "0.4.26",
"eslint-plugin-storybook": "10.3.6",
"globals": "16.5.0",
"storybook": "10.3.6",
"typescript": "5.9.3",
"typescript-eslint": "8.63.0",
"vite": "7.3.5"
"globals": "catalog:",
"storybook": "catalog:",
"typescript": "catalog:",
"typescript-eslint": "catalog:",
"vite": "catalog:"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { vi } from "vitest";

/**
* Storage boundary for the survey-reset tests. Kept in `__mocks__` (per AGENTS.md) so the `vi.mock`
* call is hoisted by the import order rather than by a bare `vi.mock` inside each spec.
*/
export const deleteResponseFileUrls = vi.fn<(fileUrls: string[], workspaceId?: string) => Promise<void>>();

vi.mock("@/modules/storage/lib/delete-response-files", () => ({
deleteResponseFileUrls,
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { TResponseData } from "@formbricks/types/responses";
import { TSurveyBlock } from "@formbricks/types/surveys/blocks";
import { TSurveyElementTypeEnum, TSurveyFileUploadElement } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";

export const surveyId = "clq5n7p1q0000m7z0h5p6g3r2";
export const workspaceId = "u8qa6u0tlxb6160pi2jb8s4p";

export const fileUploadElement: TSurveyFileUploadElement = {
id: "y3ydd3td2iq09wa599cxo1me",
type: TSurveyElementTypeEnum.FileUpload,
headline: { default: "Upload your file" },
required: false,
allowMultipleFiles: true,
};

export const fileUploadBlock: TSurveyBlock = {
id: "wq0m4wvvvhmzrxmnzmr6mkuz",
name: "File upload block",
elements: [fileUploadElement],
};

/**
* `collectSurveyResponseFileUrls` reads exactly these three fields off the survey, so the fixtures
* declare only those — fully typed, so a wrong block or element shape fails typecheck. The single cast
* to `TSurvey` lives in the mock helper that hands them to `getSurvey`.
*/
export type SurveyFileUploadFields = Pick<TSurvey, "blocks" | "questions" | "workspaceId">;

export const surveyWithFileUpload: SurveyFileUploadFields = {
blocks: [fileUploadBlock],
questions: [],
workspaceId,
};

export const surveyWithoutFileUpload: SurveyFileUploadFields = {
blocks: [],
questions: [],
workspaceId,
};

export const storageUrl = (fileName: string) =>
`https://example.com/storage/${workspaceId}/private/${fileName}`;

/** One response row as `collectSurveyResponseFileUrls` selects it (`id`, `createdAt`, `data`). */
export type ScannedResponse = { id: string; createdAt: Date; data: TResponseData };

/** Fixed epoch offsets keep the fixtures deterministic and the keyset order predictable. */
export const scanTimestamp = (index: number) => new Date(Date.UTC(2026, 0, 1) + index * 1000);

export const responseWithFiles = (id: string, fileNames: string[], index = 0): ScannedResponse => ({
id,
createdAt: scanTimestamp(index),
data: { [fileUploadElement.id]: fileNames.map(storageUrl) },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { vi } from "vitest";
import type { getSurvey as getSurveyImpl } from "@/lib/survey/service";

/**
* Survey-read boundary for the survey-reset tests. Kept in `__mocks__` (per AGENTS.md) so the
* `vi.mock` call is hoisted by import order rather than by a bare `vi.mock` in each spec. Typed off
* the real export, so `mockResolvedValue` is checked against `Promise<TSurvey | null>`.
*/
export const getSurvey = vi.fn<typeof getSurveyImpl>();

vi.mock("@/lib/survey/service", () => ({
getSurvey,
}));
Loading
Loading