diff --git a/bin/ensure-ai-cookbook.js b/bin/ensure-ai-cookbook.js
index 6599e04381..9c4dd654e9 100644
--- a/bin/ensure-ai-cookbook.js
+++ b/bin/ensure-ai-cookbook.js
@@ -4,7 +4,13 @@ const { existsSync, mkdirSync, writeFileSync } = require('fs');
const path = require('path');
const WORKSPACE_ROOT = path.resolve(__dirname, '..');
-const OUTPUT_DIR = path.join(WORKSPACE_ROOT, process.env.AI_COOKBOOK_OUTPUT_DIR ?? 'ai-cookbook');
+const outputDirFromEnv = process.env.AI_COOKBOOK_OUTPUT_DIR ?? 'ai-cookbook';
+const OUTPUT_DIR = path.resolve(WORKSPACE_ROOT, outputDirFromEnv);
+
+if (OUTPUT_DIR !== WORKSPACE_ROOT && !OUTPUT_DIR.startsWith(`${WORKSPACE_ROOT}${path.sep}`)) {
+ throw new Error('[ensure-ai-cookbook] AI_COOKBOOK_OUTPUT_DIR must resolve within the repository root');
+}
+
const PLACEHOLDER_PATH = path.join(OUTPUT_DIR, 'recipes-not-synced.mdx');
// Ensure ai-cookbook directory exists with a placeholder file.
diff --git a/src/components/elements/CallToAction.js b/src/components/elements/CallToAction.js
index 7be158145a..7ff006b384 100644
--- a/src/components/elements/CallToAction.js
+++ b/src/components/elements/CallToAction.js
@@ -1,9 +1,33 @@
import React from 'react';
import styles from './call-to-action.module.css';
+ const getSafeHref = (href) => {
+ if (typeof href !== 'string') {
+ return '#';
+ }
+
+ const value = href.trim();
+
+ if (!value || value.startsWith('/') || value.startsWith('#') || value.startsWith('?')) {
+ return value || '#';
+ }
+
+ const lowerValue = value.toLowerCase();
+
+ if (
+ lowerValue.startsWith('http://') ||
+ lowerValue.startsWith('https://') ||
+ lowerValue.startsWith('mailto:')
+ ) {
+ return value;
+ }
+
+ return '#';
+ };
+
export const CallToAction = ({ href, children }) => {
return (
-
+
{children}