Skip to content

fix: multiple utility bug fixes and hardening across lib packages - #29820

Open
PedroHenrique0713 wants to merge 4 commits into
calcom:mainfrom
PedroHenrique0713:fix/multiple-utility-bugs
Open

fix: multiple utility bug fixes and hardening across lib packages#29820
PedroHenrique0713 wants to merge 4 commits into
calcom:mainfrom
PedroHenrique0713:fix/multiple-utility-bugs

Conversation

@PedroHenrique0713

Copy link
Copy Markdown

Summary

Five bug fixes across utility libraries, each with a test that fails before the fix and passes after.

1. truncateOnWord ignores maxLength parameter (packages/lib/text.ts)

The function accepted a maxLength parameter but used a hardcoded 148 instead. Callers passing maxLength=158 (e.g., OpenGraph meta description truncation) were silently getting 148-character truncation. Fix: use maxLength instead of 148.

2. getProviderName crashes on empty location (packages/lib/CalEventParser.ts)

When location was "integrations:"" (nothing after the colon), location.split(":")[1] returned "" and ""[0].toUpperCase() threw TypeError. Fix: return empty string when provider name is empty.

3. CSV formula injection (packages/lib/csvUtils.ts)

Values starting with =, +, -, @ that contained no commas/line breaks were passed through unquoted. When opened in Excel/Sheets, these execute as formulas (OWASP CSV Injection). Fix: prefix formula-trigger characters with single quote to force text rendering.

4. extractBaseEmail produces "local@undefined" (packages/lib/extract-base-email.ts)

Without an @ sign, email.split("@") produced domain = undefined, which template literal converted to the string "undefined". This function is called from 21 call sites. Fix: guard for missing @ and return input unchanged.

5. sort() mutates input array in validation (packages/features/bookings/lib/getBookingResponsesSchema.ts:203)

emails.sort() was called in-place on the parsed data inside a Zod superRefine, mutating the input array during validation. Fix: copy before sorting with [...emails].sort().

Testing

  • npx vitest run packages/lib/text.test.ts packages/lib/csvUtils.test.ts packages/lib/CalEventParser.test.ts packages/lib/extract-base-email.test.ts — 30/30 passing

fix(text): truncateOnWord now respects its maxLength parameter instead of using hardcoded 148
fix(CalEventParser): getProviderName handles empty location strings without crashing
fix(csvUtils): protect against CSV formula injection (=, +, -, @ prefixes) per OWASP guidance
fix(extract-base-email): handle emails missing @ sign without returning 'local@undefined'
fix(getBookingResponsesSchema): avoid mutating input array with in-place sort

All fixes include tests that fail before the change and pass after.
@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @PedroHenrique0713! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b4c6a92-9f2b-4b61-b774-c4717a26a5ef

📥 Commits

Reviewing files that changed from the base of the PR and between 3bed106 and e6ffefe.

📒 Files selected for processing (2)
  • packages/lib/csvUtils.test.ts
  • packages/lib/csvUtils.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/lib/csvUtils.ts
  • packages/lib/csvUtils.test.ts

📝 Walkthrough

Walkthrough

The changes update five independent behaviors: multiemail duplicate detection avoids mutating parsed input, provider parsing handles missing integration names, CSV sanitization prefixes spreadsheet formula values, base email extraction handles strings without an at-sign, and word-aware truncation respects the supplied maximum length. New tests cover these changes along with CSV escaping, object conversion, provider parsing, email normalization, and text truncation.

Merge Risk: ⚪ Minimal · up to e6ffe

This PR makes localized utility bug fixes and hardening changes with targeted tests; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the multiple utility bug fixes and hardening changes across library packages.
Description check ✅ Passed The description clearly explains the five utility fixes, their tests, and the related validation changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/features/bookings/lib/getBookingResponsesSchema.ts (1)

203-208: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Compare against the sorted copy when detecting duplicates.

The code sorts a copy but compares against the original emails array, so unsorted duplicate inputs can be missed. Store the sorted copy and use it for both values:

Proposed fix
-    [...emails].sort().some((item, i) => {
-      if (item === emails[i + 1]) {
+    const sortedEmails = [...emails].sort();
+    sortedEmails.some((item, i) => {
+      if (item === sortedEmails[i + 1]) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/bookings/lib/getBookingResponsesSchema.ts` around lines 203
- 208, Update the duplicate detection in the email validation logic to store the
sorted copy of emails and compare each item with the next value from that same
sorted array. Keep the existing duplicate_email issue behavior and early
termination unchanged.
packages/lib/text.ts (1)

11-15: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve text when no preceding space exists.

When lastIndexOf(" ") returns -1, substring(0, -1) produces an empty string, so truncateOnWord("a".repeat(200), 100) returns only "...". Fall back to the maxLength substring when no space is found.

Proposed fix
  let truncatedText = text.substring(0, maxLength);
- truncatedText = truncatedText.substring(0, Math.min(truncatedText.length, truncatedText.lastIndexOf(" ")));
+ const lastSpaceIndex = truncatedText.lastIndexOf(" ");
+ if (lastSpaceIndex !== -1) {
+   truncatedText = truncatedText.substring(0, lastSpaceIndex);
+ }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/text.ts` around lines 11 - 15, Update truncateOnWord so it only
splits at the last space when one exists; when lastIndexOf(" ") returns -1,
retain the initial text.substring(0, maxLength) result instead of producing an
empty string.
🧹 Nitpick comments (1)
packages/lib/csvUtils.test.ts (1)

59-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove assertion-restating comments.

These comments repeat the expectations immediately below; retain only the neighboring rationale about CSV field versus record delimiters.

As per coding guidelines, “Only add code comments that explain why, not what.”

Also applies to: 77-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/csvUtils.test.ts` at line 59, Remove the assertion-restating
comments near the CSV line-count and related assertions in the test cases,
including both occurrences. Retain only the neighboring rationale explaining CSV
field versus record delimiters.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/lib/csvUtils.test.ts`:
- Around line 21-23: Update sanitizeValue to quote values containing carriage
returns by including \r in its CSV quoting condition alongside the existing
newline check. Preserve current behavior for other values and ensure row
serialization assertions pass.
- Around line 103-105: Update downloadAsCsv to create its Blob with the MIME
type text/csv;charset=utf-8 so it matches the existing createObjectURLSpy
assertion. Preserve the current CSV download behavior and avoid changing the
test expectation unless plain text is explicitly intended.

In `@packages/lib/csvUtils.ts`:
- Around line 60-65: Update the value-sanitization flow in the CSV utility so
formula-triggering prefixes are added before comma/quote CSV escaping, ensuring
values such as =SUM(1,1) remain text after import. Preserve existing escaping
behavior for non-formula values, and add a regression case covering a formula
containing a comma or quote.

In `@packages/lib/text.test.ts`:
- Around line 7-10: Update the test around truncateOnWord to use a 200-character
input containing spaces, then assert the expected truncated content for
maxLength values 158 and 100 rather than only checking length bounds. Ensure the
assertions distinguish honoring each supplied maxLength and prevent the no-space
fallback from passing with just "...".

---

Outside diff comments:
In `@packages/features/bookings/lib/getBookingResponsesSchema.ts`:
- Around line 203-208: Update the duplicate detection in the email validation
logic to store the sorted copy of emails and compare each item with the next
value from that same sorted array. Keep the existing duplicate_email issue
behavior and early termination unchanged.

In `@packages/lib/text.ts`:
- Around line 11-15: Update truncateOnWord so it only splits at the last space
when one exists; when lastIndexOf(" ") returns -1, retain the initial
text.substring(0, maxLength) result instead of producing an empty string.

---

Nitpick comments:
In `@packages/lib/csvUtils.test.ts`:
- Line 59: Remove the assertion-restating comments near the CSV line-count and
related assertions in the test cases, including both occurrences. Retain only
the neighboring rationale explaining CSV field versus record delimiters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1dcc24e7-7574-4434-86fb-4094426712d9

📥 Commits

Reviewing files that changed from the base of the PR and between 3894f37 and 53fad91.

📒 Files selected for processing (9)
  • packages/features/bookings/lib/getBookingResponsesSchema.ts
  • packages/lib/CalEventParser.test.ts
  • packages/lib/CalEventParser.ts
  • packages/lib/csvUtils.test.ts
  • packages/lib/csvUtils.ts
  • packages/lib/extract-base-email.test.ts
  • packages/lib/extract-base-email.ts
  • packages/lib/text.test.ts
  • packages/lib/text.ts

Comment thread packages/lib/csvUtils.test.ts Outdated
Comment on lines +21 to +23
it("wraps values with carriage returns in quotes", () => {
expect(sanitizeValue("hello\rworld")).toBe('"hello\rworld"');
});

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle lone carriage returns in the implementation.

This test currently fails because sanitizeValue checks \n, but not \r, and returns hello\rworld unquoted. Include \r in the CSV quoting condition; this also fixes the row assertion at lines 52-65.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/csvUtils.test.ts` around lines 21 - 23, Update sanitizeValue to
quote values containing carriage returns by including \r in its CSV quoting
condition alongside the existing newline check. Preserve current behavior for
other values and ensure row serialization assertions pass.

Comment thread packages/lib/csvUtils.test.ts Outdated
Comment on lines +103 to +105
expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
const blob = createObjectURLSpy.mock.calls[0][0] as Blob;
expect(blob.type).toBe("text/csv;charset=utf-8");

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the Blob MIME type with this assertion.

downloadAsCsv currently creates a text/plain Blob, so this assertion fails. Update it to text/csv;charset=utf-8, or change the expectation if plain text is intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/csvUtils.test.ts` around lines 103 - 105, Update downloadAsCsv
to create its Blob with the MIME type text/csv;charset=utf-8 so it matches the
existing createObjectURLSpy assertion. Preserve the current CSV download
behavior and avoid changing the test expectation unless plain text is explicitly
intended.

Comment thread packages/lib/csvUtils.ts Outdated
Comment thread packages/lib/text.test.ts Outdated
fix(text): guard against lastIndexOf returning -1 when no space in text
  truncateOnWord previously called substring(0, -1) which produces empty
  string when no space character exists within maxLength.

fix(getBookingResponsesSchema): compare against sorted copy, not original
  The previous fix created a copy for sorting but still compared items
  against the unsorted emails array, potentially missing duplicates.

fix(csvUtils.test): remove stale tests from unmerged PR calcom#29783
  The \r handling and MIME type tests belonged to PR calcom#29783 which has
  not been merged. Keep only formula injection tests from this PR.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/lib/csvUtils.test.ts (1)

29-34: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Cover formula values that also require CSV quoting.

These tests only cover formula triggers without commas, newlines, or quotes. In packages/lib/csvUtils.ts, CSV quoting runs before formula-prefix protection, so values such as =HYPERLINK("http://evil.com") or =SUM(A1,A10) can still parse as formulas. Add regression cases and apply the apostrophe before CSV escaping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/lib/csvUtils.test.ts` around lines 29 - 34, Update sanitizeValue in
csvUtils.ts to apply the apostrophe formula-protection prefix before CSV
escaping. Add regression coverage for formula values containing commas,
newlines, and embedded quotes, such as hyperlink and SUM expressions, and verify
the resulting values remain safely quoted while retaining the prefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/lib/csvUtils.test.ts`:
- Around line 29-34: Update sanitizeValue in csvUtils.ts to apply the apostrophe
formula-protection prefix before CSV escaping. Add regression coverage for
formula values containing commas, newlines, and embedded quotes, such as
hyperlink and SUM expressions, and verify the resulting values remain safely
quoted while retaining the prefix.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 61ba866e-0e9f-4a34-8d04-f884eb85dd15

📥 Commits

Reviewing files that changed from the base of the PR and between 53fad91 and 3bed106.

📒 Files selected for processing (4)
  • packages/features/bookings/lib/getBookingResponsesSchema.ts
  • packages/lib/csvUtils.test.ts
  • packages/lib/text.test.ts
  • packages/lib/text.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/lib/text.ts
  • packages/features/bookings/lib/getBookingResponsesSchema.ts
  • packages/lib/text.test.ts

@github-actions

Copy link
Copy Markdown
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

@github-actions github-actions Bot added the Stale label Jul 31, 2026
@PedroHenrique0713

Copy link
Copy Markdown
Author

Still active. State verified today:

  • mergeable: MERGEABLE against main, no conflicts.
  • The only red check is required. It fails at the step "Fail if PR is not trusted (external contributor without run-ci label)", and every other job reports skipping for the same reason. That gate lives in .github/workflows/pr.yml and needs the run-ci label from someone with write access, so it is not something I can clear from a fork. CLA, Trust Check, semgrep, labeler and PR title validation all pass.

The five fixes are independent of each other (19 test cases across 4 files):

  1. packages/lib/text.ts: truncateOnWord() ignored its maxLength argument and always cut at a hardcoded 148. It also did substring(0, Math.min(len, lastIndexOf(" "))), so a string with no space inside the window produced substring(0, -1) and the function returned just "...".
  2. packages/lib/CalEventParser.ts: getProviderName("integrations:") threw. split(":")[1] is an empty string, so locationName[0] is undefined and .toUpperCase() blows up.
  3. packages/lib/csvUtils.ts: values starting with =, +, - or @ are prefixed with a single quote, following OWASP guidance on CSV formula injection.
  4. packages/lib/extract-base-email.ts: an input with no @ returned local@undefined. It now splits on the first @ and returns the input untouched when there is none.
  5. packages/features/bookings/lib/getBookingResponsesSchema.ts: the duplicate check called emails.sort(), mutating the caller's array in place during Zod validation. It now sorts a copy and compares against that copy.

Two things I can act on:

  • One CodeRabbit finding is still open: in sanitizeValue, a value such as =SUM(1,1) hits the comma branch first and gets quoted instead of prefixed, so the formula guard never runs for it. I have not pushed the reordering, but I can add it with a test.
  • If five fixes in one PR is what makes this hard to review, I can split it per file into smaller PRs.

@github-actions

Copy link
Copy Markdown
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

@github-actions github-actions Bot added the Stale label Aug 13, 2026
The =/+/-/@ guard ran after the quoting branches, so a formula that also
contains a comma or a quote was returned quoted but without the ' prefix
and is still evaluated by Excel/Sheets. CSV quoting is a delimiter rule,
not an escape the spreadsheet honours.

Adds a test covering =SUM(1,1), a quoted HYPERLINK payload and a formula
containing a newline. It fails on the previous implementation.
@PedroHenrique0713

Copy link
Copy Markdown
Author

Just pushed the fix for the incomplete formula-injection guard CodeRabbit flagged.

The =/+/-/@ check was running after the quoting branches, so =SUM(1,1) matched the comma branch first and came back quoted but without the ' prefix - still evaluated as a formula by Excel and Sheets. The guard now runs first, and the quoting branches operate on the already-prefixed value. Added a test covering =SUM(1,1), a quoted =HYPERLINK("...","click") payload and a formula containing a newline; it fails on the previous implementation and passes on this one. 28 tests green across the four files this PR touches, formatting checked with Biome.

The rest of the PR is unchanged and still applies to main as of today - I re-read the files before posting:

  • packages/lib/text.ts L11 still truncates at a hardcoded 148 instead of maxLength.
  • packages/lib/extract-base-email.ts still returns local@undefined for input with no @.
  • packages/lib/CalEventParser.ts L202-206 still does location.split(":")[1] then locationName[0].toUpperCase(), so a bare integrations: throws on the empty string.
  • packages/features/bookings/lib/getBookingResponsesSchema.ts L203 still calls emails.sort() in place during validation.

State: MERGEABLE against main. CLA, Trust Check, CodeRabbit, semgrep, labeler and Validate PR title are green. The red required check fails at "Fail if PR is not trusted (external contributor without run-ci label)" and takes the rest of the jobs down as skipped - that needs a maintainer to add the run-ci label, nothing I can do from a fork.

Worth knowing before merge: this PR and #29783 both add packages/lib/csvUtils.test.ts, so whichever lands first will conflict with the other.

@github-actions github-actions Bot removed the Stale label Aug 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

csvUtils.ts was being edited by this PR and by calcom#29783 at the same time, and the
two conflict on sanitizeValue. calcom#29783 is the PR dedicated to that file, so the
formula-injection guard and its tests now live there and this PR keeps the four
unrelated fixes.

This also retires the two review comments on csvUtils.test.ts: the file is no
longer part of this PR.
PedroHenrique0713 added a commit to PedroHenrique0713/cal.diy that referenced this pull request Sep 1, 2026
sanitizeValue was being changed by two open PRs at once (calcom#29783 and calcom#29820),
which conflict on this exact block. Consolidating both csvUtils changes here so
one PR owns the function: line breaks (LF/CR/CRLF) and the Blob MIME type, plus
the OWASP formula-injection prefix that calcom#29820 carried.

The prefix runs before quoting on purpose: quoting is a CSV delimiter, not an
escape a spreadsheet honours, so a formula that also contains a comma would come
back merely quoted and still be evaluated.
@pull-request-size pull-request-size Bot removed the size/L label Sep 1, 2026
@PedroHenrique0713

Copy link
Copy Markdown
Author

Removed the csvUtils changes from this PR and moved them to #29783, which is the PR dedicated to that file. The two were editing sanitizeValue at the same time and conflicted on it, so keeping them in one place avoids handing a reviewer a merge conflict between two of my own PRs.

That also retires the two open review comments on csvUtils.test.ts — the file is no longer part of this PR.

What remains here is the four unrelated fixes: getBookingResponsesSchema, CalEventParser, extract-base-email and text. 19 tests passing locally across the three touched test files.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant