From f41f1ae2509cf58b885762a245b6d9a62c96ab60 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Sat, 31 Oct 2026 23:23:16 -0600 Subject: [PATCH] fix(docs): accept scoped generated reference paths --- src/utils/repo-path.ts | 2 +- tests/scoped-docs-paths.test.ts | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/scoped-docs-paths.test.ts diff --git a/src/utils/repo-path.ts b/src/utils/repo-path.ts index e47bfc7a4..aaefe17ec 100644 --- a/src/utils/repo-path.ts +++ b/src/utils/repo-path.ts @@ -1,6 +1,6 @@ export const MAX_REPO_PATH_LENGTH = 512 -const REPO_PATH_SEGMENT_PATTERN = /^[a-zA-Z0-9._$+\[\]()-]+$/ +const REPO_PATH_SEGMENT_PATTERN = /^[a-zA-Z0-9@._$+\[\]()-]+$/ export function isValidRepoPath(path: string) { if (path === '') { diff --git a/tests/scoped-docs-paths.test.ts b/tests/scoped-docs-paths.test.ts new file mode 100644 index 000000000..51c920913 --- /dev/null +++ b/tests/scoped-docs-paths.test.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { isValidRepoPath } from '../src/utils/repo-path' +import { resolveDocsPathRedirect } from '../src/utils/docs-redirects' + +const docsPath = + 'reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD' +test('scoped generated reference paths pass both content and route validation', () => { + assert.equal(isValidRepoPath(`docs/${docsPath}.md`), true) + assert.deepEqual( + resolveDocsPathRedirect({ + defaultDocs: 'overview', + docsPath, + frameworks: [], + manifest: { paths: [docsPath], redirects: {} }, + }), + { type: 'render', docsPath }, + ) +}) +test('scoped path support preserves traversal and URL delimiter rejection', () => { + for (const path of [ + '@scope/../secret', + '@scope/./secret', + '@scope//secret', + '@scope/%2e%2e/secret', + '@scope/file?raw=1', + '@scope/file#fragment', + '@scope\\secret', + '//user@host/file', + 'https://user@host/file', + ]) { + assert.equal(isValidRepoPath(path), false, path) + } +})