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}