Skip to content
Open
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
11 changes: 2 additions & 9 deletions packages/app-store/zohocalendar/api/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
import config from "../config.json";
import { getValidZohoDomain } from "../lib/zoho-domains";
import type { ZohoAuthCredentials } from "../types/ZohoCalendar";
import { appKeysSchema as zohoKeysSchema } from "../zod";

Expand Down Expand Up @@ -53,15 +54,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
code,
};

let server_location;

if (location === "us") {
server_location = "com";
} else if (location === "au") {
server_location = "com.au";
} else {
server_location = location;
}
const server_location = getValidZohoDomain(location);

const query = stringify(params);

Expand Down
24 changes: 24 additions & 0 deletions packages/app-store/zohocalendar/lib/zoho-domains.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";

import { getValidZohoDomain } from "./zoho-domains";

describe("getValidZohoDomain", () => {
it.each([
["us", "com"],
["eu", "eu"],
["in", "in"],
["cn", "com.cn"],
["jp", "jp"],
["au", "com.au"],
])("maps location '%s' to domain '%s'", (location, expected) => {
expect(getValidZohoDomain(location)).toBe(expected);
});

it("falls back to 'com' for undefined location", () => {
expect(getValidZohoDomain(undefined)).toBe("com");
});

it("falls back to 'com' for unknown location", () => {
expect(getValidZohoDomain("evil.com")).toBe("com");
});
});
15 changes: 15 additions & 0 deletions packages/app-store/zohocalendar/lib/zoho-domains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Valid Zoho data center domains per https://www.zoho.com/crm/developer/docs/api/v6/multi-dc.html
const LOCATION_TO_DOMAIN: Record<string, string> = {
us: "com",
eu: "eu",
in: "in",
cn: "com.cn",
jp: "jp",
au: "com.au",
};

const DEFAULT_DOMAIN = "com";

export function getValidZohoDomain(location: string | undefined): string {
return LOCATION_TO_DOMAIN[location || "us"] || DEFAULT_DOMAIN;
}
Comment on lines +13 to +15
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify current behavior with prototype-inherited key vs safe hasOwn check.
node <<'NODE'
const LOCATION_TO_DOMAIN = { us:"com", eu:"eu", in:"in", cn:"com.cn", jp:"jp", au:"com.au" };
const probe = "toString";

const current = LOCATION_TO_DOMAIN[probe] || "com";
const safe = Object.prototype.hasOwnProperty.call(LOCATION_TO_DOMAIN, probe)
  ? LOCATION_TO_DOMAIN[probe]
  : "com";

console.log("current_type:", typeof current);
console.log("current_preview:", String(current).slice(0, 60));
console.log("safe_value:", safe);
NODE

Repository: calcom/cal.com

Length of output: 151


🏁 Script executed:

# Check if the file exists and read the relevant code
cat -n packages/app-store/zohocalendar/lib/zoho-domains.ts | head -30

Repository: calcom/cal.com

Length of output: 572


🏁 Script executed:

# Search for usages of getValidZohoDomain to understand the threat surface
rg "getValidZohoDomain" --type ts --type tsx -B 2 -A 2

Repository: calcom/cal.com

Length of output: 85


🏁 Script executed:

# Check if LOCATION_TO_DOMAIN is accessed elsewhere in unsafe ways
rg "LOCATION_TO_DOMAIN" --type ts --type tsx -B 2 -A 2

Repository: calcom/cal.com

Length of output: 85


🏁 Script executed:

# Find usages of getValidZohoDomain with simpler rg command
rg "getValidZohoDomain" -A 2 -B 2

Repository: calcom/cal.com

Length of output: 3081


🏁 Script executed:

# Also check if location parameter comes from user input (query params, request body, etc.)
rg "location" packages/app-store/zohocalendar --type-not json -A 1 -B 1 | head -50

Repository: calcom/cal.com

Length of output: 3801


Prototype-key inputs can bypass unknown-location fallback.

In packages/app-store/zohocalendar/api/callback.ts, the location parameter comes directly from req.query. Prototype keys like "toString" will resolve inherited Object properties, allowing non-domain values to flow into URL construction. The current fallback LOCATION_TO_DOMAIN[location || "us"] || DEFAULT_DOMAIN does not filter these.

🛠️ Proposed fix
-const LOCATION_TO_DOMAIN: Record<string, string> = {
+const LOCATION_TO_DOMAIN = {
   us: "com",
   eu: "eu",
   in: "in",
   cn: "com.cn",
   jp: "jp",
   au: "com.au",
-};
+} as const;
+type ZohoLocation = keyof typeof LOCATION_TO_DOMAIN;

 const DEFAULT_DOMAIN = "com";

+function isZohoLocation(location: string): location is ZohoLocation {
+  return Object.prototype.hasOwnProperty.call(LOCATION_TO_DOMAIN, location);
+}
+
 export function getValidZohoDomain(location: string | undefined): string {
-  return LOCATION_TO_DOMAIN[location || "us"] || DEFAULT_DOMAIN;
+  if (!location) return DEFAULT_DOMAIN;
+  return isZohoLocation(location) ? LOCATION_TO_DOMAIN[location] : DEFAULT_DOMAIN;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getValidZohoDomain(location: string | undefined): string {
return LOCATION_TO_DOMAIN[location || "us"] || DEFAULT_DOMAIN;
}
const LOCATION_TO_DOMAIN = {
us: "com",
eu: "eu",
in: "in",
cn: "com.cn",
jp: "jp",
au: "com.au",
} as const;
type ZohoLocation = keyof typeof LOCATION_TO_DOMAIN;
const DEFAULT_DOMAIN = "com";
function isZohoLocation(location: string): location is ZohoLocation {
return Object.prototype.hasOwnProperty.call(LOCATION_TO_DOMAIN, location);
}
export function getValidZohoDomain(location: string | undefined): string {
if (!location) return DEFAULT_DOMAIN;
return isZohoLocation(location) ? LOCATION_TO_DOMAIN[location] : DEFAULT_DOMAIN;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-store/zohocalendar/lib/zoho-domains.ts` around lines 13 - 15,
The getValidZohoDomain function currently indexes LOCATION_TO_DOMAIN with the
raw location value which allows prototype keys like "toString" to resolve
inherited properties; update getValidZohoDomain to first ensure location is a
plain string and then check membership using a safe own-property check (e.g.,
Object.prototype.hasOwnProperty.call(LOCATION_TO_DOMAIN, location) or
Object.hasOwn) before returning LOCATION_TO_DOMAIN[location]; if the check fails
return DEFAULT_DOMAIN (still allow the existing "us" default when location is
undefined). Reference getValidZohoDomain, LOCATION_TO_DOMAIN, and DEFAULT_DOMAIN
when making the change.

Loading