Skip to content

[785] fix: resolve 403 Forbidden error on form submission in production - #212

Merged
VitalyyP merged 5 commits into
mainfrom
785-fix/403-forbidden-form-submission-in-production
Aug 15, 2025
Merged

[785] fix: resolve 403 Forbidden error on form submission in production#212
VitalyyP merged 5 commits into
mainfrom
785-fix/403-forbidden-form-submission-in-production

Conversation

@VitalyyP

Copy link
Copy Markdown
Contributor
  • Add CSRF token to form widget template
  • Add CORS configuration for production environment

- Add CSRF token to form widget template
- Add CORS configuration for production environment
- Ensure proper session and CSRF handling for production deployment
@VitalyyP
VitalyyP requested a review from killev as a code owner August 12, 2025 12:17
@VitalyyP VitalyyP self-assigned this Aug 12, 2025
@coderabbitai

coderabbitai Bot commented Aug 12, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Added a CORS configuration block to the @apostrophecms/express module in website/app.js (origin set to https://speedandfunction.com in production, otherwise allow all; credentials enabled; methods: GET, POST, PUT, DELETE, OPTIONS; headers: Content-Type, Authorization, X-Requested-With, X-CSRF-Token). Added a hidden CSRF input to website/modules/@apostrophecms/form-widget/views/widget.html to submit apos.csrfToken with form submissions. No public API or signature changes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Suggested reviewers

  • yuramax
  • Anton-88
  • killev

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8ac6992 and 352b2b9.

📒 Files selected for processing (1)
  • website/app.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • website/app.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e-tests
  • GitHub Check: lint
  • GitHub Check: unit-tests
  • GitHub Check: security-scan
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 785-fix/403-forbidden-form-submission-in-production

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions

github-actions Bot commented Aug 12, 2025

Copy link
Copy Markdown

🔍 Vulnerabilities of apostrophe-cms:test

📦 Image Reference apostrophe-cms:test
digestsha256:5b5c49de633641f644c9b03f79b750c3481a9cb7eb5c537bc15a59de44221be5
vulnerabilitiescritical: 1 high: 4 medium: 0 low: 0
platformlinux/amd64
size291 MB
packages984
📦 Base Image node:23-alpine
also known as
  • 23-alpine3.22
  • 23.11-alpine
  • 23.11-alpine3.22
  • 23.11.1-alpine
  • 23.11.1-alpine3.22
digestsha256:b9d38d589853406ff0d4364f21969840c3e0397087643aef8eede40edbb6c7cd
vulnerabilitiescritical: 0 high: 0 medium: 1 low: 1
critical: 1 high: 0 medium: 0 low: 0 form-data 4.0.2 (npm)

pkg:npm/form-data@4.0.2

critical 9.4: CVE--2025--7783 Use of Insufficiently Random Values

Affected range>=4.0.0
<4.0.4
Fixed version4.0.4
CVSS Score9.4
CVSS VectorCVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N
EPSS Score0.076%
EPSS Percentile23rd percentile
Description

Summary

form-data uses Math.random() to select a boundary value for multipart form-encoded data. This can lead to a security issue if an attacker:

  1. can observe other values produced by Math.random in the target application, and
  2. can control one field of a request made using form-data

Because the values of Math.random() are pseudo-random and predictable (see: https://blog.securityevaluators.com/hacking-the-javascript-lottery-80cc437e3b7f), an attacker who can observe a few sequential values can determine the state of the PRNG and predict future values, includes those used to generate form-data's boundary value. The allows the attacker to craft a value that contains a boundary value, allowing them to inject additional parameters into the request.

This is largely the same vulnerability as was recently found in undici by parrot409 -- I'm not affiliated with that researcher but want to give credit where credit is due! My PoC is largely based on their work.

Details

The culprit is this line here: https://github.com/form-data/form-data/blob/426ba9ac440f95d1998dac9a5cd8d738043b048f/lib/form_data.js#L347

An attacker who is able to predict the output of Math.random() can predict this boundary value, and craft a payload that contains the boundary value, followed by another, fully attacker-controlled field. This is roughly equivalent to any sort of improper escaping vulnerability, with the caveat that the attacker must find a way to observe other Math.random() values generated by the application to solve for the state of the PRNG. However, Math.random() is used in all sorts of places that might be visible to an attacker (including by form-data itself, if the attacker can arrange for the vulnerable application to make a request to an attacker-controlled server using form-data, such as a user-controlled webhook -- the attacker could observe the boundary values from those requests to observe the Math.random() outputs). A common example would be a x-request-id header added by the server. These sorts of headers are often used for distributed tracing, to correlate errors across the frontend and backend. Math.random() is a fine place to get these sorts of IDs (in fact, opentelemetry uses Math.random for this purpose)

PoC

PoC here: https://github.com/benweissmann/CVE-2025-7783-poc

Instructions are in that repo. It's based on the PoC from https://hackerone.com/reports/2913312 but simplified somewhat; the vulnerable application has a more direct side-channel from which to observe Math.random() values (a separate endpoint that happens to include a randomly-generated request ID).

Impact

For an application to be vulnerable, it must:

  • Use form-data to send data including user-controlled data to some other system. The attacker must be able to do something malicious by adding extra parameters (that were not intended to be user-controlled) to this request. Depending on the target system's handling of repeated parameters, the attacker might be able to overwrite values in addition to appending values (some multipart form handlers deal with repeats by overwriting values instead of representing them as an array)
  • Reveal values of Math.random(). It's easiest if the attacker can observe multiple sequential values, but more complex math could recover the PRNG state to some degree of confidence with non-sequential values.

If an application is vulnerable, this allows an attacker to make arbitrary requests to internal systems.

critical: 0 high: 1 medium: 0 low: 0 async 0.9.2 (npm)

pkg:npm/async@0.9.2

high 7.8: CVE--2021--43138 OWASP Top Ten 2017 Category A9 - Using Components with Known Vulnerabilities

Affected range<2.6.4
Fixed version2.6.4, 3.2.2
CVSS Score7.8
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Score0.907%
EPSS Percentile75th percentile
Description

A vulnerability exists in Async through 3.2.1 (fixed in 3.2.2), which could let a malicious user obtain privileges via the mapValues() method.

critical: 0 high: 1 medium: 0 low: 0 linkifyjs 4.2.0 (npm)

pkg:npm/linkifyjs@4.2.0

high 8.8: CVE--2025--8101 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

Affected range<4.3.2
Fixed version4.3.2
CVSS Score8.8
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:L/SC:N/SI:N/SA:N
EPSS Score0.060%
EPSS Percentile19th percentile
Description

Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') vulnerability in Linkify (linkifyjs) allows XSS Targeting HTML Attributes and Manipulating User-Controlled Variables.This issue affects Linkify: from 4.3.1 before 4.3.2.

critical: 0 high: 1 medium: 0 low: 0 connect-multiparty 2.2.0 (npm)

pkg:npm/connect-multiparty@2.2.0

high 7.8: CVE--2022--29623 Unrestricted Upload of File with Dangerous Type

Affected range<=2.2.0
Fixed versionNot Fixed
CVSS Score7.8
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Score0.320%
EPSS Percentile54th percentile
Description

An arbitrary file upload vulnerability in the file upload module of Express Connect-Multiparty 2.2.0 allows attackers to execute arbitrary code via a crafted PDF file. NOTE: the Supplier has not verified this vulnerability report.

critical: 0 high: 1 medium: 0 low: 0 async 1.5.2 (npm)

pkg:npm/async@1.5.2

high 7.8: CVE--2021--43138 OWASP Top Ten 2017 Category A9 - Using Components with Known Vulnerabilities

Affected range<2.6.4
Fixed version2.6.4, 3.2.2
CVSS Score7.8
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Score0.907%
EPSS Percentile75th percentile
Description

A vulnerability exists in Async through 3.2.1 (fixed in 3.2.2), which could let a malicious user obtain privileges via the mapValues() method.

@coderabbitai coderabbitai 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.

Actionable comments posted: 0

🧹 Nitpick comments (3)
website/app.js (2)

35-51: CORS block is on the right track; tighten prod origins, align with BASE_URL, and include common CSRF header variants

  • In production, consider allowing both apex and www subdomain to avoid broken submissions if traffic hits www.
  • Prefer deriving allowed origins from BASE_URL to avoid hard-coding.
  • Narrow methods to what you actually need (likely GET, POST, OPTIONS for the form submit) and add maxAge to reduce preflight chatter.
  • Include common CSRF header variants that csurf accepts (x-xsrf-token, csrf-token) to avoid surprises with different clients.

Apply this diff within the CORS block:

-          // Add CORS configuration for production
-          cors: {
-            origin: (() => {
-              if (process.env.NODE_ENV === 'production') {
-                return 'https://speedandfunction.com';
-              }
-              return true;
-            })(),
-            credentials: true,
-            methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
-            allowedHeaders: [
-              'Content-Type',
-              'Authorization',
-              'X-Requested-With',
-              'X-CSRF-Token',
-            ],
-          },
+          // CORS configuration (restrict origins in production)
+          cors: {
+            origin: (() => {
+              if (process.env.NODE_ENV === 'production') {
+                const base = getEnv('BASE_URL');
+                // Allow apex and www variants of BASE_URL in prod
+                const u = new URL(base);
+                const apex = `${u.protocol}//${u.hostname}`;
+                const www = u.hostname.startsWith('www.')
+                  ? `${u.protocol}//${u.hostname.replace(/^www\./, '')}`
+                  : `${u.protocol}//www.${u.hostname}`;
+                return [apex, www];
+              }
+              // Reflect request origin in non-prod
+              return true;
+            })(),
+            credentials: true,
+            methods: ['GET', 'POST', 'OPTIONS'],
+            allowedHeaders: [
+              'Content-Type',
+              'Authorization',
+              'X-Requested-With',
+              'X-CSRF-Token',
+              'X-XSRF-Token',
+              'CSRF-Token'
+            ],
+            // Cache preflight for a day
+            maxAge: 86400
+          },

35-35: Nit: comment implies “for production” but config applies to all envs

The block is active in all environments (prod restricts origin, non-prod reflects any). Consider updating the comment for clarity.

-          // Add CORS configuration for production
+          // CORS configuration (prod: restricted origins; non-prod: reflect any origin)
website/modules/@apostrophecms/form-widget/views/widget.html (1)

23-23: Optional: guard for missing token to avoid empty submission in edge cases

If CSRF is ever disabled or not injected on a route, this would emit an empty hidden field. It won’t break, but we can conditionally render it.

-    <input type="hidden" name="_csrf" value="{{ apos.csrfToken }}" />
+    {% if apos.csrfToken %}
+      <input type="hidden" name="_csrf" value="{{ apos.csrfToken }}" />
+    {% endif %}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 336cb19 and 4566e67.

📒 Files selected for processing (2)
  • website/app.js (1 hunks)
  • website/modules/@apostrophecms/form-widget/views/widget.html (1 hunks)
🔇 Additional comments (2)
website/app.js (1)

25-34: SameSite 'lax' is appropriate – no external form hosts detected

We searched for any HTML forms posting to off-site domains and found none. All client-side fetch calls either use relative URLs (e.g. form.action) or hit known server-side endpoints (infinite scroll, reCAPTCHA). Therefore, keeping

sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',

is correct for your current topology.

website/modules/@apostrophecms/form-widget/views/widget.html (1)

23-23: Good fix: hidden CSRF field matches server expectations

Using <input type="hidden" name="_csrf" value="{{ apos.csrfToken }}" /> aligns with the csurf defaults and with your cookie key. This should address the 403s from missing token on POST.

@VitalyyP
VitalyyP requested a review from Anton-88 August 14, 2025 10:54
@VitalyyP
VitalyyP enabled auto-merge (squash) August 14, 2025 10:57

@Anton-88 Anton-88 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.

LGTM

@vasilyyaremchuk

Copy link
Copy Markdown
Collaborator

@VitalyyP could you rebase the branch against main to be merged without conflicts?

@sonarqubecloud

Copy link
Copy Markdown

@vasilyyaremchuk

Copy link
Copy Markdown
Collaborator

LGTM

@VitalyyP
VitalyyP merged commit dcb132f into main Aug 15, 2025
12 checks passed
@VitalyyP
VitalyyP deleted the 785-fix/403-forbidden-form-submission-in-production branch August 15, 2025 05:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants