kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: add blacklist for namespaces (usernames + team names)
rodzic
0f6fcff943
commit
fbb9863208
|
@ -1,5 +1,6 @@
|
||||||
import { assert } from '@agentic/platform-core'
|
import { assert } from '@agentic/platform-core'
|
||||||
import {
|
import {
|
||||||
|
isNamespaceAllowed,
|
||||||
isValidCuid,
|
isValidCuid,
|
||||||
isValidDeploymentIdentifier,
|
isValidDeploymentIdentifier,
|
||||||
isValidProjectIdentifier,
|
isValidProjectIdentifier,
|
||||||
|
@ -82,12 +83,20 @@ export const usernameSchema = z
|
||||||
.refine((username) => isValidUsername(username), {
|
.refine((username) => isValidUsername(username), {
|
||||||
message: 'Invalid username'
|
message: 'Invalid username'
|
||||||
})
|
})
|
||||||
|
.refine((username) => isNamespaceAllowed(username), {
|
||||||
|
message:
|
||||||
|
'Username is not allowed (reserved, offensive, or otherwise confusing)'
|
||||||
|
})
|
||||||
|
|
||||||
export const teamSlugSchema = z
|
export const teamSlugSchema = z
|
||||||
.string()
|
.string()
|
||||||
.refine((slug) => isValidTeamSlug(slug), {
|
.refine((slug) => isValidTeamSlug(slug), {
|
||||||
message: 'Invalid team 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({
|
export const paginationSchema = z.object({
|
||||||
offset: z.number().int().nonnegative().default(0).optional(),
|
offset: z.number().int().nonnegative().default(0).optional(),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './namespace-blacklist'
|
||||||
export * from './parse-deployment-identifier'
|
export * from './parse-deployment-identifier'
|
||||||
export * from './parse-project-identifier'
|
export * from './parse-project-identifier'
|
||||||
export * from './parse-tool-identifier'
|
export * from './parse-tool-identifier'
|
||||||
|
|
|
@ -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'
|
||||||
|
])
|
|
@ -1,6 +1,7 @@
|
||||||
import { expect, test } from 'vitest'
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
isNamespaceAllowed,
|
||||||
isValidDeploymentHash,
|
isValidDeploymentHash,
|
||||||
isValidDeploymentIdentifier,
|
isValidDeploymentIdentifier,
|
||||||
isValidEmail,
|
isValidEmail,
|
||||||
|
@ -11,19 +12,28 @@ import {
|
||||||
isValidUsername
|
isValidUsername
|
||||||
} from './validators'
|
} from './validators'
|
||||||
|
|
||||||
test('email success', () => {
|
test('isValidEmail success', () => {
|
||||||
expect(isValidEmail('t@t.com')).toBe(true)
|
expect(isValidEmail('t@t.com')).toBe(true)
|
||||||
expect(isValidEmail('abc@gmail.com')).toBe(true)
|
expect(isValidEmail('abc@gmail.com')).toBe(true)
|
||||||
expect(isValidEmail('abc@foo.io')).toBe(true)
|
expect(isValidEmail('abc@foo.io')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('email failure', () => {
|
test('isValidEmail failure', () => {
|
||||||
expect(isValidEmail('t@t')).toBe(false)
|
expect(isValidEmail('t@t')).toBe(false)
|
||||||
expect(isValidEmail('abc')).toBe(false)
|
expect(isValidEmail('abc')).toBe(false)
|
||||||
expect(isValidEmail('@')).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('z')).toBe(true)
|
||||||
expect(isValidUsername('abc')).toBe(true)
|
expect(isValidUsername('abc')).toBe(true)
|
||||||
expect(isValidUsername('abc-123')).toBe(true)
|
expect(isValidUsername('abc-123')).toBe(true)
|
||||||
|
@ -32,7 +42,7 @@ test('username success', () => {
|
||||||
expect(isValidUsername('a'.repeat(256))).toBe(true)
|
expect(isValidUsername('a'.repeat(256))).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('username failure (invalid)', () => {
|
test('isValidUsername failure (invalid)', () => {
|
||||||
expect(isValidUsername('ab%')).toBe(false)
|
expect(isValidUsername('ab%')).toBe(false)
|
||||||
expect(isValidUsername('.')).toBe(false)
|
expect(isValidUsername('.')).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)
|
expect(isValidUsername('a'.repeat(257))).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('password success', () => {
|
test('isValidPassword success', () => {
|
||||||
expect(isValidPassword('abc')).toBe(true)
|
expect(isValidPassword('abc')).toBe(true)
|
||||||
expect(isValidPassword('password')).toBe(true)
|
expect(isValidPassword('password')).toBe(true)
|
||||||
expect(isValidPassword('asldfkjasldkfjlaksdfjlkas')).toBe(true)
|
expect(isValidPassword('asldfkjasldkfjlaksdfjlkas')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('password failure', () => {
|
test('isValidPassword failure', () => {
|
||||||
expect(isValidPassword('aa')).toBe(false)
|
expect(isValidPassword('aa')).toBe(false)
|
||||||
expect(isValidPassword('.'))
|
expect(isValidPassword('.'))
|
||||||
expect(isValidPassword('a'.repeat(1025))).toBe(false)
|
expect(isValidPassword('a'.repeat(1025))).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('projectName success', () => {
|
test('isValidProjectName success', () => {
|
||||||
expect(isValidProjectName('a')).toBe(true)
|
expect(isValidProjectName('a')).toBe(true)
|
||||||
expect(isValidProjectName('ai')).toBe(true)
|
expect(isValidProjectName('ai')).toBe(true)
|
||||||
expect(isValidProjectName('aaa')).toBe(true)
|
expect(isValidProjectName('aaa')).toBe(true)
|
||||||
|
@ -62,7 +72,7 @@ test('projectName success', () => {
|
||||||
expect(isValidProjectName('f'.repeat(256))).toBe(true)
|
expect(isValidProjectName('f'.repeat(256))).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('projectName failure', () => {
|
test('isValidProjectName failure', () => {
|
||||||
expect(isValidProjectName('hello_world')).toBe(false)
|
expect(isValidProjectName('hello_world')).toBe(false)
|
||||||
expect(isValidProjectName('a_bc')).toBe(false)
|
expect(isValidProjectName('a_bc')).toBe(false)
|
||||||
expect(isValidProjectName('abc.')).toBe(false)
|
expect(isValidProjectName('abc.')).toBe(false)
|
||||||
|
@ -72,25 +82,25 @@ test('projectName failure', () => {
|
||||||
expect(isValidProjectName('f'.repeat(257))).toBe(false)
|
expect(isValidProjectName('f'.repeat(257))).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('deploymentHash success', () => {
|
test('isValidDeploymentHash success', () => {
|
||||||
expect(isValidDeploymentHash('abcdefgh')).toBe(true)
|
expect(isValidDeploymentHash('abcdefgh')).toBe(true)
|
||||||
expect(isValidDeploymentHash('01234567')).toBe(true)
|
expect(isValidDeploymentHash('01234567')).toBe(true)
|
||||||
expect(isValidDeploymentHash('k2l3n6l2')).toBe(true)
|
expect(isValidDeploymentHash('k2l3n6l2')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('deploymentHash failure', () => {
|
test('isValidDeploymentHash failure', () => {
|
||||||
expect(isValidDeploymentHash('aa')).toBe(false)
|
expect(isValidDeploymentHash('aa')).toBe(false)
|
||||||
expect(isValidDeploymentHash('')).toBe(false)
|
expect(isValidDeploymentHash('')).toBe(false)
|
||||||
expect(isValidDeploymentHash('Abcdefgh')).toBe(false)
|
expect(isValidDeploymentHash('Abcdefgh')).toBe(false)
|
||||||
expect(isValidDeploymentHash('012345678')).toBe(false)
|
expect(isValidDeploymentHash('012345678')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('projectIdentifier success', () => {
|
test('isValidProjectIdentifier success', () => {
|
||||||
expect(isValidProjectIdentifier('@username/project-name')).toBe(true)
|
expect(isValidProjectIdentifier('@username/project-name')).toBe(true)
|
||||||
expect(isValidProjectIdentifier('@a/123')).toBe(true)
|
expect(isValidProjectIdentifier('@a/123')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('projectIdentifier failure', () => {
|
test('isValidProjectIdentifier failure', () => {
|
||||||
expect(isValidProjectIdentifier('')).toBe(false)
|
expect(isValidProjectIdentifier('')).toBe(false)
|
||||||
expect(isValidProjectIdentifier()).toBe(false)
|
expect(isValidProjectIdentifier()).toBe(false)
|
||||||
expect(isValidProjectIdentifier('foo')).toBe(false)
|
expect(isValidProjectIdentifier('foo')).toBe(false)
|
||||||
|
@ -108,7 +118,7 @@ test('projectIdentifier failure', () => {
|
||||||
expect(isValidProjectIdentifier('@_/___')).toBe(false)
|
expect(isValidProjectIdentifier('@_/___')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('deploymentIdentifier success', () => {
|
test('isValidDeploymentIdentifier success', () => {
|
||||||
expect(isValidDeploymentIdentifier('@username/project-name@01234567')).toBe(
|
expect(isValidDeploymentIdentifier('@username/project-name@01234567')).toBe(
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
@ -125,7 +135,7 @@ test('deploymentIdentifier success', () => {
|
||||||
expect(isValidDeploymentIdentifier('@a/123')).toBe(true)
|
expect(isValidDeploymentIdentifier('@a/123')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('deploymentIdentifier failure', () => {
|
test('isValidDeploymentIdentifier failure', () => {
|
||||||
expect(isValidDeploymentIdentifier('')).toBe(false)
|
expect(isValidDeploymentIdentifier('')).toBe(false)
|
||||||
expect(isValidDeploymentIdentifier()).toBe(false)
|
expect(isValidDeploymentIdentifier()).toBe(false)
|
||||||
expect(isValidDeploymentIdentifier('foo')).toBe(false)
|
expect(isValidDeploymentIdentifier('foo')).toBe(false)
|
||||||
|
@ -144,7 +154,7 @@ test('deploymentIdentifier failure', () => {
|
||||||
expect(isValidDeploymentIdentifier('012345678/123@1.0.1')).toBe(false)
|
expect(isValidDeploymentIdentifier('012345678/123@1.0.1')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('toolName success', () => {
|
test('isValidToolName success', () => {
|
||||||
expect(isValidToolName('tool_name')).toBe(true)
|
expect(isValidToolName('tool_name')).toBe(true)
|
||||||
expect(isValidToolName('toolName')).toBe(true)
|
expect(isValidToolName('toolName')).toBe(true)
|
||||||
expect(isValidToolName('_identIFIER0123')).toBe(true)
|
expect(isValidToolName('_identIFIER0123')).toBe(true)
|
||||||
|
@ -155,7 +165,7 @@ test('toolName success', () => {
|
||||||
expect(isValidToolName('_searchGoogle')).toBe(true)
|
expect(isValidToolName('_searchGoogle')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('toolName failure', () => {
|
test('isValidToolName failure', () => {
|
||||||
expect(isValidToolName('ab1.2')).toBe(false)
|
expect(isValidToolName('ab1.2')).toBe(false)
|
||||||
expect(isValidToolName('foo-bar')).toBe(false)
|
expect(isValidToolName('foo-bar')).toBe(false)
|
||||||
expect(isValidToolName('abc/123')).toBe(false)
|
expect(isValidToolName('abc/123')).toBe(false)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { isCuid } from '@paralleldrive/cuid2'
|
||||||
import emailValidator from 'email-validator'
|
import emailValidator from 'email-validator'
|
||||||
|
|
||||||
import type { ParseIdentifierOptions } from './types'
|
import type { ParseIdentifierOptions } from './types'
|
||||||
|
import { namespaceBlacklist } from './namespace-blacklist'
|
||||||
import { parseDeploymentIdentifier } from './parse-deployment-identifier'
|
import { parseDeploymentIdentifier } from './parse-deployment-identifier'
|
||||||
import { parseProjectIdentifier } from './parse-project-identifier'
|
import { parseProjectIdentifier } from './parse-project-identifier'
|
||||||
|
|
||||||
|
@ -21,6 +22,10 @@ export function isValidNamespace(value?: string): boolean {
|
||||||
return !!value && namespaceRe.test(value)
|
return !!value && namespaceRe.test(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isNamespaceAllowed(value?: string): boolean {
|
||||||
|
return !!value && isValidNamespace(value) && !namespaceBlacklist.has(value)
|
||||||
|
}
|
||||||
|
|
||||||
export function isValidUsername(value?: string): boolean {
|
export function isValidUsername(value?: string): boolean {
|
||||||
return isValidNamespace(value)
|
return isValidNamespace(value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,6 @@
|
||||||
- raw
|
- raw
|
||||||
- auth
|
- auth
|
||||||
- custom auth pages for `openauth`
|
- custom auth pages for `openauth`
|
||||||
- add username / team name blacklist
|
|
||||||
- admin, internal, mcp, sse, etc
|
|
||||||
- API gateway
|
- API gateway
|
||||||
- how to handle binary bodies and responses?
|
- how to handle binary bodies and responses?
|
||||||
- add support for `immutable` in `toolConfigs`
|
- add support for `immutable` in `toolConfigs`
|
||||||
|
|
Ładowanie…
Reference in New Issue