diff --git a/packages/utilities/src/usernames.ts b/packages/utilities/src/usernames.ts index 50f467315..6be5d76dc 100644 --- a/packages/utilities/src/usernames.ts +++ b/packages/utilities/src/usernames.ts @@ -497,9 +497,10 @@ const FORBIDDEN_USERNAMES_REGEXPS = [ 'security\\.txt', 'llms\\.txt', 'llms-full\\.txt', - 'AGENTS\\.md', - 'CLAUDE\\.md', - 'auth\\.md', + // apify.com serves a markdown twin of each page at .md, so any username ending + // in ".md" collides with that route. Blocked here rather than in USERNAME.REGEX so that + // existing holders keep their username; the regex runs on every user-record write. + '(.*\\.md)', // All hidden files '(\\..*)', diff --git a/test/usernames.test.ts b/test/usernames.test.ts index 61df1a865..b31a22fa0 100644 --- a/test/usernames.test.ts +++ b/test/usernames.test.ts @@ -22,12 +22,17 @@ describe('isForbiddenUsername()', () => { expect(isForbiddenUsername('BingSiteAuth.XML')).toBe(true); expect(isForbiddenUsername('llms.txt')).toBe(true); expect(isForbiddenUsername('llms-full.txt')).toBe(true); + + // Any username ending in ".md" collides with the markdown twin of the page expect(isForbiddenUsername('AGENTS.md')).toBe(true); - expect(isForbiddenUsername('agents.MD')).toBe(true); expect(isForbiddenUsername('CLAUDE.md')).toBe(true); - expect(isForbiddenUsername('claude.MD')).toBe(true); expect(isForbiddenUsername('auth.md')).toBe(true); - expect(isForbiddenUsername('AUTH.MD')).toBe(true); + expect(isForbiddenUsername('foo.md')).toBe(true); + expect(isForbiddenUsername('foo.MD')).toBe(true); + expect(isForbiddenUsername('.md')).toBe(true); + expect(isForbiddenUsername('foo.mdx')).toBe(false); + expect(isForbiddenUsername('foo.markdown')).toBe(false); + expect(isForbiddenUsername('md.foo')).toBe(false); // Agentic protocols and payment standards expect(isForbiddenUsername('x402')).toBe(true);