diff --git a/website/src/components/anchor-modal.js b/website/src/components/anchor-modal.js
index 8fdc8e4..2382ce1 100644
--- a/website/src/components/anchor-modal.js
+++ b/website/src/components/anchor-modal.js
@@ -106,21 +106,29 @@ export function closeModal() {
}
}
+const SAFE_ANCHOR_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
+const SAFE_LANG = /^[a-z]{2}$/
+
export async function loadAnchorContent(anchorId) {
const modal = document.getElementById('anchor-modal')
const titleEl = modal.querySelector('#modal-title')
const contentEl = modal.querySelector('#modal-content')
+ if (!SAFE_ANCHOR_ID.test(anchorId)) {
+ contentEl.innerHTML = '
Invalid anchor ID.
'
+ return
+ }
+
try {
// Try language-specific file first (e.g., tdd-london-school.de.adoc for German)
const currentLang = i18n.currentLang()
let response
- if (currentLang !== 'en') {
+ const safeLang = SAFE_LANG.test(currentLang) ? currentLang : 'en'
+
+ if (safeLang !== 'en') {
// Try fetching language-specific anchor file
- response = await fetch(
- `${import.meta.env.BASE_URL}docs/anchors/${anchorId}.${currentLang}.adoc`
- )
+ response = await fetch(`${import.meta.env.BASE_URL}docs/anchors/${anchorId}.${safeLang}.adoc`)
// If language-specific file not found, fallback to English
if (!response.ok) {
diff --git a/website/src/utils/router.js b/website/src/utils/router.js
index cc0aa6a..7c750e5 100644
--- a/website/src/utils/router.js
+++ b/website/src/utils/router.js
@@ -51,6 +51,9 @@ function handleRoute() {
// Check for anchor route (#/anchor/:id)
if (path.startsWith('/anchor/')) {
const anchorId = path.replace('/anchor/', '')
+ const safeAnchorId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(anchorId) ? anchorId : null
+ if (!safeAnchorId) return
+
// Navigate to home first
const homeHandler = routes.get('/')
if (homeHandler) {
@@ -60,7 +63,7 @@ function handleRoute() {
// Then open the anchor modal
// Import dynamically to avoid circular dependency
import('../components/anchor-modal.js').then(({ showAnchorDetails }) => {
- showAnchorDetails(anchorId)
+ showAnchorDetails(safeAnchorId)
})
return
}
diff --git a/website/tests/e2e/website.spec.js b/website/tests/e2e/website.spec.js
index 7f333e3..8989d9d 100644
--- a/website/tests/e2e/website.spec.js
+++ b/website/tests/e2e/website.spec.js
@@ -142,7 +142,7 @@ test.describe('Homepage - Card Grid', () => {
const editBtn = firstCard.locator('.anchor-edit-btn')
await expect(editBtn).toBeVisible()
- await expect(editBtn).toHaveAttribute('href', /github\.com.*edit/)
+ await expect(editBtn).toHaveAttribute('href', /^https:\/\/github\.com\/.+\/edit\/.+/)
})
test('should display action links', async ({ page }) => {