Project Report – Anonymous & Secure Web form
Course: COMP4911
June 30, 2026
Prepared by:
Radwan Ahmed – T00741911
Table of Contents
- Introduction. 3
- Development Methodology 4
- Requirements 5
- Analysis 8
- System Design. 12
- Implementation. 16
- Testing. 17
- Conclusion. 21
- References 22
1. Introduction
This report summarizes the steps that were taken to develop an anonymous web form built for a non-profit. I was tasked to develop a web portal for people who identify as LGBTQ+, to document their experiences while navigating their faith-based journey. Since this is a sensitive topic and that the information provided could expose their identity, it’s important that the privacy of these individuals is protected.
The project was divided into distinct phases. The first phase involved determining the appropriate development model, where I had opted for an agile iterative development model. The next step involved requirements gathering, which involved determining the list of functional and non-functional requirements, and which requirements were necessary for a minimal viable product (MVP). Third, I then had then explored 5 different implementation options, where I ultimately decided on implementing the solution using Formbricks. The next phase involved the design phase, where I had outlined the architecture of the system, which is best described as a three-tier monolithic structure with a proxy tier, application tier, and data tier layer. Lastly, the final phase involved implementing the solution, and testing it against the functional and non-functional requirements. The final result is a self-hosted Formbricks instance running behind a Traefik reverse proxy using TLS, that’s deployed on a Google Cloud virtual machine. The final solution satisfies all of the must-have functional and non-functional requirements, which is further described below.
2. Development Methodology
The project adopted an agile based iterative development model, with two-week sprints, for a 12-13-week timeline duration. This would mean that development would end roughly mid-June. The implementation and testing of the solution had finished on June 19, which is roughly 1-week past the proposed timeline, that was established on March 20, 2026. The two development processes analyzed included the waterfall approach, as well as the iterative approach. Furthermore, I also explored Agile methodology, which embodies a set of principles such as iterative and incremental development, customer collaboration, and adaptability to change. I ultimately chose the agile iterative approach to allow for greater flexibility if the project requirements needed to change during development. Furthermore, each completed cycle allows an opportunity for the client to offer their input, ensuring rigorous refinement that is more tailored to the client’s needs. This will ensure that the project remains faithfully aligned with the non-profit’s evolving needs. Incorporating user feedback continuously during the design had proven useful, as during the requirements phase, the client had introduced a new must-have requirement (NFR-09), which states the solution must be maintainable by someone with limited coding knowledge. This greatly influenced the decision during the analysis phase to choose Formbricks over other methods, due to its user-friendly no-code user interface. If the waterfall method had been chosen, it’s possible that the client would not have been satisfied with the final product, since the waterfall method is more resistant to change.
Github had been chosen for version control, and it is the chosen tool used to store the operating instructions for the non-profit. The README.md can be reached at https://github.com/cattleherd/Secure-Form. This acts as the central repository for administrators seeking instructions on operating the cloud server instance, as well as managing forms. Furthermore, instead of relying on Zoom meetings, I opted for regular emails to the non-profit for status updates, and to get feedback on each incremental change.
3. Requirements
The final requirements list was formed by the client’s initial specifications, as well as my own personal research into Canadian privacy law on the handling of user data, such as Personal Information Protection and Electronic Documents Act (PIPEDA). The client’s initial specification had asked for user anonymity, no third-party access, no identifying data collection in the form of cookies, and IP addresses, and secure hosting at reduced cost. While the non-profits are exempt from PIPEDA due to its non-profit status, due to the sensitivity of the data being handled, PIPEDA’s ten fair information principles were referenced as a framework for responsible data handling.
The requirements were split into functional and non-functional requirements, and further categorized into “must have,” “nice to have,” and “stretch goal”. All of the must-have requirements must be present in the final MVP.
Table 1 – MVP “must have” functional requirements
| Functional Requirements | |
| ID | Requirement |
| FR-01 | The user shall be able to fill out and submit a form anonymously. |
| FR-02 | The system shall have a privacy policy showing what is collected and how data is handled. |
| FR-05 | The administrator shall securely log in to view submissions. |
| FR-06 | The administrator shall be able to read entries. |
| FR-07 | The admin shall permanently delete submissions if needed to. |
Table 2 – MVP “must have” non-functional requirements
| Non-Functional Requirements | |
| ID | Requirement |
| NFR-01 | There shall be no user IP addresses logged by the system. |
| NFR-02 | The system shall not store cookies or other fingerprinting technologies. |
| NFR-03 | All data shall be transmitted over HTTPS/TLS. |
| NFR-04 | The submitted data shall be encrypted at rest. |
| NFR-05 | The system shall not transmit data to third parties. |
| NFR-06 | The system shall sanitize user input to prevent injection attacks. |
| NFR-09 | The solution shall be maintainable by someone with limited coding knowledge. |
4. Analysis
During this phase, 5 different implementation approaches were considered, and the chosen solution was selected based on its alignment with the must-have requirements, as well as being feasible given the 12-13 week timeframe.
The first approach considered was to use a built-from-scratch approach. The tech stack would use Next.js and ReactJs, incorporating a cloud database, and contained within a docker container. This would allow the solution to be hosted behind a reverse proxy such as Nginx, and served on any cloud server. While this approach offered full control over features, this immediately conflicted against NFR-09. This is because this solution requires considerable technical expertise to maintain, and implementing a no-code content management interface would have not been feasible given the 13-week timeframe. Due to those reasons, other options were considered.
The next approach was to use Baserow as the main platform. On the surface this approach initially looked promising due to its no-code user interface (UI), and privacy considerations (enabling configuration for encryption for data at rest and in transit). However, the main reason why this solution wasn’t selected was because the solution is mainly a database tool. For a non-technical person, the database approach for managing submissions would be more difficult to sift through and analyse, especially for many submissions. Ultimately, this solution wasn’t selected due to going against NFR-09.
The third option explored was CryptPad. This solution comes with end-to-end encryption, meaning encryption and decryption happen only in the user’s browser and the server only stores ciphertext (encrypted data). This differs from other solutions, where encrypted data remains at rest, but it must load the data as plaintext in memory to view or display it. However, despite its strong security features, it failed FR-07, which details that the admin must be able to delete submissions if needed. The deletion process on Cryptpad is tricky because the document history is compacted rather than fully erased. The only true workaround in fully deleting all traces of a submissions is to make a copy of the form after deleting a submission, which does erase the document history, but it also changes the form’s URL. Due to this impractical workaround, other options were still explored.
Nextcloud is a solution that offered TLS, encryption at rest, and authentication, however, it remains being a full productivity suite. It is meant to be a real-time collaboration platform with a suite of tools such as file management, a calendar, and video conferencing. Having all of these tools even though the project scope was to just serve a form meant that this solution would have unnecessary overhead. This would unnecessarily increase the running costs of running the server due to needing more compute resources. Thus, for this reason, this solution was ruled out for a more cost-efficient solution.
Formbricks became the chosen solution. It is an open-source, survey platform tool that’s built on Next.js with a PostgreSQL backend, which is lightweight. It satisfied every must-have requirement, which is detailed below in the following traceability table:
Table 3 – Requirements traceability (FR & NFR)
| Requirements Traceability Matrix | ||
| ID | Requirement | Status / How |
| FR-01 | The user shall be able to fill out and submit a form anonymously. | The client has agreed to remove identifying data in submissions. |
| FR-02 | The system shall have a privacy policy showing what is collected and how data is handled. | Disclosed in the form, and it is agreed upon via a consent checkbox. |
| FR-05 | The administrator shall securely log in to view submissions. | Formbricks implements authentication via NextAuth. |
| FR-06 | The administrator shall be able to read entries. | The admin dashboard shows a list of all entries. |
| FR-07 | The admin shall permanently delete submissions if needed to. | Formbricks has a permanent delete feature, which wipes the submission from the Postgres DB table. |
| NFR-01 | There shall be no user IP addresses logged by the system. | Self-hosting is implemented and google cloud console does not log IP’s by default. Submissions are named ‘anonymous’ as well. |
| NFR-02 | The system shall not store cookies or other fingerprinting technologies. | Self-hosting is implemented, which does not log IPs or cookies. |
| NFR-03 | All data shall be transmitted over HTTPS/TLS. | Traefik reverse proxy sits at the edge, which intercepts all incoming internet traffic. HTTP requests are redirected to HTTPS. |
| NFR-04 | The submitted data shall be encrypted at rest. | The server hosting platform implements disk encryption at rest. |
| NFR-05 | The system shall not transmit data to third parties. | This is a self-hosting setup. |
| NFR-06 | The system shall sanitize user input to prevent injection attacks. | Formbricks implements Prisma to handle SQL queries to be stored in the Postgres database, which prevents SQL injections by default. The ReactJs frontend also prevents cross-site scripting, preventing malicious payloads when an admin views submissions. |
| NFR-09 | The solution shall be maintainable by someone with limited coding knowledge. | A graphical user interface (GUI) allows an admin to create, edit, and delete forms and review submissions without writing code. Furthermore, a README document has been included as well to instruct users on routine tasks such as starting and stopping the server, and managing forms. |
5. System Design
The design of the system resembles a three-tier monolithic architecture. The first tier is the reverse proxy (Traefik), which sits at the edge, and intercepts all internet traffic to the server. The second tier is the application layer, which is Formbricks itself. It handles admin authentication and rendering the form management interface, as well as for displaying the form to users. The app layer also manages database updates via its application programming interface (API). The database tier acts as the final layer, which is responsible for storing form submissions, and admin account credentials. Further, these tiers are deployed within Docker using Docker Compose, which is a containerization platform that allows for easy deployment onto a cloud server platform. The cloud platform used in this case is Google Cloud virtual private server (VPS), and the physical server is hosted in Iowa to reduce costs, however, depending on budget and security requirements, the VPS can be provisioned in Canada.
A monolithic design is the appropriate design for this use case as it allows for better maintainability, and its better suited for the scope of the project (serving a simple web form). A single unit designs does lack robustness – for example, if the server does go offline, the form would be unavailable. But since the form is a low-traffic form, this is an acceptable trade-off.
The reverse proxy (Traefik) is situated at the edge of the network, and it is the only component exposed to the internet. It listens to ports 80 and 443, and all requests arriving on plain HTTP via port 80 is automatically redirected (via the browser) to port 443 (HTTPS) on the same domain. This forces all web traffic towards the server over the encrypted channel.
When a user connects to the form, their browser starts a TLS handshake with the server’s reverse proxy (Traefik). The browser begins with a ‘ClientHello’ message, which shows the TLS versions, cipher suites, and other security parameters it supports. Traefik then responds with a ServerHello, by choosing the TLS version and cipher suite, and presenting its Let’s Encrypt certificate. The browser then validates the certificate on whether it is legitimate, and then the browser and server perform a key exchange. First the browser computes and sends a shared secret key and encrypts it using the server’s public key, that way only the server can decrypt it when it receives it. Once received, both sides can now conduct encrypted communication, and a user can then submit a form response securely. The TLS handshake process is required for establishing encrypted communication between the client and server, for secure data exchange.
After a successful TLS handshake between the user and server, when the user fills out and submits the form, the data is sent across the internet as ciphertext, which is “… designed to prevent eavesdropping and tampering” [15]. Once the ciphertext arrives at the server, it is intercepted by Traefik which sits at the edge. It then “…terminates the incoming TLS connection, inspects the HTTP plaintext” [15] and sends the plaintext to the Formbricks application within the Docker network. This internal network is not reachable via the internet, since all ports (except those exposed by Traefik) are closed to the internet.
Formbricks then processes the data and stores an entry in the PostgreSQL database. Finally, the data is then stored on a Google Cloud disk that is encrypted at rest by default, until it needs to be accessed again.
In summary, data is encrypted in transit via the internet and is only read as plaintext once inside the internal network. Once the data is no longer accessed, data is cleared from the server’s memory and encrypted at rest in the server’s hard disk. However, it is important to note that it may still be possible for an attacker to intercept the plaintext data if they gained access to the server while the data was still loaded in memory. This is an inherent limitation of server-side encryption (instead of) end-to-end encryption. This is because the application must store the plaintext data in memory to process it. However, this attack surface has a limited window, it would require hacking the running server itself instead of simply intercepting network traffic. And given that we’ve already included encryption in transit and at rest, and a containerized internal network with no exposed ports (except the reverse proxy), this risk is manageable.
6. Implementation
The initial design was to run the form on an e2-micro instance (2 vCPU shared, 1 GB RAM), however, during the implementation I realized that the minimum requirements turned out to be 2 GB RAM, which meant having to upgrade to an e2-small instance. This would mean that hosting the form would not be free, and would cost roughly 30$ a month to run 24/7. I also specified a disk size of 30GB, running Debian 12 (Bookworm), and the physical server located at the us-central1 region (Iowa). I also removed the Ops Agent (which collects monitoring data of the server), and automated backups to keep costs as low as possible. The external IP was changed from ephemeral to static, which meant that the external IP of the server would remain fixed, ensuring lookups to the form domain would always connect to the correct address. I opted to use DuckDNS to provide a free domain (https://rainbowfaithandfreedom.duckdns.org/) that points to the server’s external IP. Since 2 GB ram is the minimum requirements, I opted to also add 2GB swap memory as well as an added assurance if the instances needed more than 2GB ram during heavy loads.
As for deployment onto the server hardware, Docker and Docker Compose were first installed using Docker’s official repository. I then downloaded the Formbricks’ installation files. The application also needed configuration before deployment, such as generating shared secret keys, and configuring Docker to recognize Formbricks and its services. Afterwards, I integrated Traefik as the reverse proxy to sit at the edge of the network. This configuration step was done manually, since Formbricks included a 1-click setup, but I opted to see if there was a way to implement it myself to understand how it works under the hood. The 1-click setup is in the form of a script (https://github.com/formbricks/formbricks/blob/main/docker/formbricks.sh), and from there I was able to determine how it wires everything together, to replicate that setup manually. Once the reverse proxy was configured, I was then able to start all services using a single command (docker compose up).
- Testing
I opted to use a black-box testing strategy, which is a testing approach “…that examines the functionality of an application without peering into its internal structures or workings” [16]. Since this approach relies on pre-built components, the goal is to verify whether the solution works as intended. Thus, each of the must-have functional and non-functional requirements will be traceable to at least one test to ensure alignment with the client’s goals, and security standards outlined during the research phase.
The tests were organised into two categories, functional tests, and security/privacy tests. Each test and its result is summarized, and if applicable, traced back to either a must-have requirement (needed for the MVP), or traced to the original exhaustive list of functional and non-functional requirements outlined in the initial requirements report.
Table 4 – Functional Tests Plan
| Functional Tests Plan | |||
| ID | Test | Result | Traceability |
| T-01 | Fill out and submit the form from a browser and checking if the submission was stored as an anonymous submission. | A submission was logged in the admin panel, and no identifying information was logged besides the form’s content. A success message is displayed after submission, with a link that returns the user to the non-profit’s main page. | FR-01 (Must Have) |
| T-02 | Attempt to submit the form without acknowledging privacy / consent. | Submission is blocked and the checkbox highlighted in red until consent is given. | FR-02 (Must Have), FR-03 (Nice to Have) |
| T-03 | Create an admin account, login securely, and view the admin dashboard and the submission list. | Admin account successfully created, and able to login and view all submitted entries as a readable list. | FR-05 (Must Have), FR-06 (Must Have). |
| T-04 | Permanently delete a submission, then confirm with the dashboard. | Entry is removed from the dashboard. | FR-07 (Must Have) |
| T-05 | Perform all form operations using only the GUI. | A non-technical user could maintain the form without writing code. | NFR-09 (Must Have) |
Table 5 – Security & Privacy Test Plan
| Security & Privacy Test Plan | |||
| ID | Test | Result | Traceability |
| T-06 | Run docker compose ps to confirm which services expose ports to the internet. | Only Traefik publishes ports 80 and 443; Formbricks, PostgreSQL, Redis, Cube, and Hub remain on the internal Docker network. | NFR-05 (Must Have) |
| T-07 | Attempt direct connections to the internal service ports (3000, 6379, 5432) at the VM’s public IP. | All connection attempts timed out, confirming internal services are unreachable from the internet. | NFR-05 (Must Have) |
| T-08 | Send a plain HTTP request to the form’s hostname. | Request returned 308 Permanent Redirect to HTTPS, confirming no data is transmitted unencrypted. | NFR-03 (Must Have) |
| T-09 | Send an HTTPS request to the form’s hostname. | Request returned HTTP/2 200, confirming a working encrypted connection. | NFR-03 (Must Have) |
| T-10 | Trigger a TLS 1.0 handshake with the server. | Handshake was rejected, confirming the minimum TLS 1.2 policy is enforced. | NFR-03 (Must Have) |
| T-11 | Determine whether Traefik has access logs enabled (which stores IP addresses). | Confirm access logging is not enabled. Confirmed using `docker compose config | grep -i accesslog`. | NFR-01 (Must Have) |
8. Conclusion
The deliverables provided to the client were a secure web form hosted that’s self-hosted on a Google operated VM, a detailed README on GitHub, and a video how-to guide. The README and how-to video were extremely useful for the hand-off process allowing the non-profit to manage daily operation of the system themselves. The purpose of this project to provide individuals of the community an anonymous outlet to feel free to share their experiences and feel heard, without being afraid of their privacy exposed, or their data compromised.
The agile iterative model was useful in the development process, such as checking in with the client during key cycles during development. Notably during the analysis phase, the client had introduced a new requirement NFR-09, which is a testament to the value of the agile iterative model which embraces change and client feedback. The greatest skill that I had learned from this entire process was developing hustle. This meant having to source and secure a viable client on my own, maintaining the relationship, and ensuring the project moved forward despite hurdles. In one example, my original supervisor left the company halfway through, and I had to find a new contact to report to, and rebuild that relationship from scratch, and bring them up to speed on the project. The project resulted in a live demo that I was able to present during the Pride parade at their booth, where I tested the form with real users. People were eager to engage with something I built, which provided an extra sense of satisfaction. Furthermore, the client was also pleased with the result and even offered the possibility of paid opportunities going forward. This experience gave me direct experience on the end-to-end process on self-managing a real project, as well as engaging with stakeholders.
- References
[1] Docker, “Install Docker Engine on Debian,” Docker Inc. [Online]. Available: https://docs.docker.com/engine/install/debian/. [Accessed: Jun. 6, 2026].
[2] Google Cloud, “Create and start a Compute Engine instance,” Google. [Online]. Available: https://docs.cloud.google.com/compute/docs/instances/create-start-instance. [Accessed: Jun. 6, 2026].
[3] Formbricks, “Self-hosting overview,” Formbricks. [Online]. Available: https://formbricks.com/docs/self-hosting/overview. [Accessed: Jun. 10, 2026].
[4] L. Tesar, “Create a Linux swap file,” Linuxize. [Online]. Available: https://linuxize.com/post/create-a-linux-swap-file/. [Accessed: Jun. 11, 2026].
[5] Formbricks, “Docker setup,” Formbricks. [Online]. Available: https://formbricks.com/docs/self-hosting/setup/docker. [Accessed: Jun. 13, 2026].
[6] Formbricks, “Domain configuration,” Formbricks. [Online]. Available: https://formbricks.com/docs/self-hosting/configuration/domain-configuration. [Accessed: Jun. 18, 2026].
[7] GNU, “sed, a stream editor,” Free Software Foundation. [Online]. Available: https://www.gnu.org/software/sed/manual/sed.html. [Accessed: Jun. 18, 2026].
[8] Docker, “Use Traefik as a reverse proxy,” Docker Inc. [Online]. Available: https://docs.docker.com/guides/traefik/. [Accessed: Jun. 18, 2026].
[9] Traefik Labs, “Getting started with Docker and Traefik,” Traefik Labs. [Online]. Available: https://doc.traefik.io/traefik/getting-started/quick-start-with-docker/. [Accessed: Jun. 18, 2026].
[10] Traefik Labs, “Docker-compose with Let’s Encrypt: TLS challenge,” Traefik Labs. [Online]. Available: https://doc.traefik.io/traefik/v3.3/user-guides/docker-compose/acme-tls/. [Accessed: Jun. 18, 2026].
[11] Docker, “Port publishing and mapping,” Docker Inc. [Online]. Available: https://docs.docker.com/engine/network/port-publishing/. [Accessed: Jun. 18, 2026].
[12] Formbricks, “formbricks.sh,” GitHub. [Online]. Available: https://github.com/formbricks/formbricks/blob/main/docker/formbricks.sh. [Accessed: Jun. 18, 2026].
[13] Formbricks, “Custom SSL Certificate,” Formbricks. [Online]. Available: https://formbricks.com/docs/self-hosting/configuration/custom-ssl. [Accessed: Jun. 18, 2026].
[14] Traefik Labs, “Configuration Overview,” Traefik Labs. [Online]. Available: https://doc.traefik.io/traefik/getting-started/configuration-overview/. [Accessed: Jun. 18, 2026].
[15] Wikipedia contributors, “Transport Layer Security,” Wikipedia, The Free Encyclopedia. [Online]. Available: https://en.wikipedia.org/wiki/Transport_Layer_Security. [Accessed: Jun. 22, 2026].
[16] Wikipedia contributors, “Black-box testing,” Wikipedia, The Free Encyclopedia. [Online]. Available: https://en.wikipedia.org/wiki/Black-box_testing. [Accessed: Jun. 24, 2026].