feat: add blacklist for namespaces (usernames + team names)

pull/715/head
Travis Fischer 2025-06-08 00:36:20 +07:00
rodzic 0f6fcff943
commit fbb9863208
6 zmienionych plików z 158 dodań i 18 usunięć

Wyświetl plik

@ -1,5 +1,6 @@
import { assert } from '@agentic/platform-core'
import {
isNamespaceAllowed,
isValidCuid,
isValidDeploymentIdentifier,
isValidProjectIdentifier,
@ -82,12 +83,20 @@ export const usernameSchema = z
.refine((username) => isValidUsername(username), {
message: 'Invalid username'
})
.refine((username) => isNamespaceAllowed(username), {
message:
'Username is not allowed (reserved, offensive, or otherwise confusing)'
})
export const teamSlugSchema = z
.string()
.refine((slug) => isValidTeamSlug(slug), {
message: 'Invalid team slug'
})
.refine((slug) => isNamespaceAllowed(slug), {
message:
'Team slug is not allowed (reserved, offensive, or otherwise confusing)'
})
export const paginationSchema = z.object({
offset: z.number().int().nonnegative().default(0).optional(),

Wyświetl plik

@ -1,3 +1,4 @@
export * from './namespace-blacklist'
export * from './parse-deployment-identifier'
export * from './parse-project-identifier'
export * from './parse-tool-identifier'

Wyświetl plik

@ -0,0 +1,117 @@
export const namespaceBlacklist = new Set([
// restricted because they would be confusing
'agentic',
'admin',
'root',
'sudo',
'mcp',
'sse',
'raw',
'todo',
'team',
'api',
'user',
'ai',
'llm',
'agent',
'agents',
'free',
'paid',
'tool',
'tools',
'not',
'openapi',
'error',
'legal',
'legacy',
'support',
'email',
'help',
'faq',
'privacy',
'terms',
'about',
'contact',
'internal',
'public',
'private',
'404',
'500',
'403',
'401',
'400',
'409',
'408',
'407',
'406',
// bad words
'fuck',
'shit',
'bitch',
'cunt',
'dick',
'pussy',
'ass',
'asshole',
'faggot',
'fag',
'fagot',
'nigger',
'nigga',
'niggers',
'niggas',
'niggers',
'porn',
'sex',
'anal',
'blowjob',
'cum',
'penis',
'vagina',
'boobs',
'tits',
// reserved for companies
'openai',
'anthropic',
'google',
'microsoft',
'meta',
'alibaba',
'amazon',
'apple',
'arista',
'atlassian',
'aws',
'azure',
'cloudflare',
'cisco',
'stripe',
'vercel',
'facebook',
'twitter',
'x',
'instagram',
'tiktok',
'youtube',
'linkedin',
'github',
'gitlab',
'bitbucket',
'slack',
'discord',
'telegram',
'whatsapp',
'snapchat',
'pinterest',
'reddit',
'tumblr',
'pornhub',
'xvideos',
// reserved for individuals
'trump',
'elon',
'elonmusk'
])

Wyświetl plik

@ -1,6 +1,7 @@
import { expect, test } from 'vitest'
import {
isNamespaceAllowed,
isValidDeploymentHash,
isValidDeploymentIdentifier,
isValidEmail,
@ -11,19 +12,28 @@ import {
isValidUsername
} from './validators'
test('email success', () => {
test('isValidEmail success', () => {
expect(isValidEmail('t@t.com')).toBe(true)
expect(isValidEmail('abc@gmail.com')).toBe(true)
expect(isValidEmail('abc@foo.io')).toBe(true)
})
test('email failure', () => {
test('isValidEmail failure', () => {
expect(isValidEmail('t@t')).toBe(false)
expect(isValidEmail('abc')).toBe(false)
expect(isValidEmail('@')).toBe(false)
})
test('username success', () => {
test('isNamespaceAllowed', () => {
expect(isNamespaceAllowed('foo')).toBe(true)
expect(isNamespaceAllowed('foo-bar')).toBe(true)
expect(isNamespaceAllowed('vercel')).toBe(false)
expect(isNamespaceAllowed('ai')).toBe(false)
expect(isNamespaceAllowed('fuck')).toBe(false)
expect(isNamespaceAllowed('agentic')).toBe(false)
})
test('isValidUsername success', () => {
expect(isValidUsername('z')).toBe(true)
expect(isValidUsername('abc')).toBe(true)
expect(isValidUsername('abc-123')).toBe(true)
@ -32,7 +42,7 @@ test('username success', () => {
expect(isValidUsername('a'.repeat(256))).toBe(true)
})
test('username failure (invalid)', () => {
test('isValidUsername failure (invalid)', () => {
expect(isValidUsername('ab%')).toBe(false)
expect(isValidUsername('.')).toBe(false)
expect(isValidUsername('$')).toBe(false)
@ -41,19 +51,19 @@ test('username failure (invalid)', () => {
expect(isValidUsername('a'.repeat(257))).toBe(false)
})
test('password success', () => {
test('isValidPassword success', () => {
expect(isValidPassword('abc')).toBe(true)
expect(isValidPassword('password')).toBe(true)
expect(isValidPassword('asldfkjasldkfjlaksdfjlkas')).toBe(true)
})
test('password failure', () => {
test('isValidPassword failure', () => {
expect(isValidPassword('aa')).toBe(false)
expect(isValidPassword('.'))
expect(isValidPassword('a'.repeat(1025))).toBe(false)
})
test('projectName success', () => {
test('isValidProjectName success', () => {
expect(isValidProjectName('a')).toBe(true)
expect(isValidProjectName('ai')).toBe(true)
expect(isValidProjectName('aaa')).toBe(true)
@ -62,7 +72,7 @@ test('projectName success', () => {
expect(isValidProjectName('f'.repeat(256))).toBe(true)
})
test('projectName failure', () => {
test('isValidProjectName failure', () => {
expect(isValidProjectName('hello_world')).toBe(false)
expect(isValidProjectName('a_bc')).toBe(false)
expect(isValidProjectName('abc.')).toBe(false)
@ -72,25 +82,25 @@ test('projectName failure', () => {
expect(isValidProjectName('f'.repeat(257))).toBe(false)
})
test('deploymentHash success', () => {
test('isValidDeploymentHash success', () => {
expect(isValidDeploymentHash('abcdefgh')).toBe(true)
expect(isValidDeploymentHash('01234567')).toBe(true)
expect(isValidDeploymentHash('k2l3n6l2')).toBe(true)
})
test('deploymentHash failure', () => {
test('isValidDeploymentHash failure', () => {
expect(isValidDeploymentHash('aa')).toBe(false)
expect(isValidDeploymentHash('')).toBe(false)
expect(isValidDeploymentHash('Abcdefgh')).toBe(false)
expect(isValidDeploymentHash('012345678')).toBe(false)
})
test('projectIdentifier success', () => {
test('isValidProjectIdentifier success', () => {
expect(isValidProjectIdentifier('@username/project-name')).toBe(true)
expect(isValidProjectIdentifier('@a/123')).toBe(true)
})
test('projectIdentifier failure', () => {
test('isValidProjectIdentifier failure', () => {
expect(isValidProjectIdentifier('')).toBe(false)
expect(isValidProjectIdentifier()).toBe(false)
expect(isValidProjectIdentifier('foo')).toBe(false)
@ -108,7 +118,7 @@ test('projectIdentifier failure', () => {
expect(isValidProjectIdentifier('@_/___')).toBe(false)
})
test('deploymentIdentifier success', () => {
test('isValidDeploymentIdentifier success', () => {
expect(isValidDeploymentIdentifier('@username/project-name@01234567')).toBe(
true
)
@ -125,7 +135,7 @@ test('deploymentIdentifier success', () => {
expect(isValidDeploymentIdentifier('@a/123')).toBe(true)
})
test('deploymentIdentifier failure', () => {
test('isValidDeploymentIdentifier failure', () => {
expect(isValidDeploymentIdentifier('')).toBe(false)
expect(isValidDeploymentIdentifier()).toBe(false)
expect(isValidDeploymentIdentifier('foo')).toBe(false)
@ -144,7 +154,7 @@ test('deploymentIdentifier failure', () => {
expect(isValidDeploymentIdentifier('012345678/123@1.0.1')).toBe(false)
})
test('toolName success', () => {
test('isValidToolName success', () => {
expect(isValidToolName('tool_name')).toBe(true)
expect(isValidToolName('toolName')).toBe(true)
expect(isValidToolName('_identIFIER0123')).toBe(true)
@ -155,7 +165,7 @@ test('toolName success', () => {
expect(isValidToolName('_searchGoogle')).toBe(true)
})
test('toolName failure', () => {
test('isValidToolName failure', () => {
expect(isValidToolName('ab1.2')).toBe(false)
expect(isValidToolName('foo-bar')).toBe(false)
expect(isValidToolName('abc/123')).toBe(false)

Wyświetl plik

@ -2,6 +2,7 @@ import { isCuid } from '@paralleldrive/cuid2'
import emailValidator from 'email-validator'
import type { ParseIdentifierOptions } from './types'
import { namespaceBlacklist } from './namespace-blacklist'
import { parseDeploymentIdentifier } from './parse-deployment-identifier'
import { parseProjectIdentifier } from './parse-project-identifier'
@ -21,6 +22,10 @@ export function isValidNamespace(value?: string): boolean {
return !!value && namespaceRe.test(value)
}
export function isNamespaceAllowed(value?: string): boolean {
return !!value && isValidNamespace(value) && !namespaceBlacklist.has(value)
}
export function isValidUsername(value?: string): boolean {
return isValidNamespace(value)
}

Wyświetl plik

@ -28,8 +28,6 @@
- raw
- auth
- custom auth pages for `openauth`
- add username / team name blacklist
- admin, internal, mcp, sse, etc
- API gateway
- how to handle binary bodies and responses?
- add support for `immutable` in `toolConfigs`