Skip to main content
Glama
prisma

Prisma MCP Server

Official
by prisma
security.test.ts16 kB
import { describe, expect, it } from 'vitest' import { parameterizeQuery } from '../parameterize' describe('parameterizeQuery - security: no user data leakage', () => { const sensitiveValues = [ 'password123', 'secret-api-key', 'user@private.email', 'SSN-123-45-6789', 'credit-card-1234-5678', '{"sensitive":"data"}', ] describe('where clauses', () => { it('does not leak string values in where clauses', () => { for (const sensitive of sensitiveValues) { const query = { arguments: { where: { field: sensitive } }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitive) } }) it('does not leak numeric values that could be IDs', () => { const sensitiveId = 98765432 const query = { arguments: { where: { id: sensitiveId } }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(String(sensitiveId)) }) it('does not leak values in nested where conditions', () => { const sensitive = 'secret-nested-value' const query = { arguments: { where: { user: { profile: { bio: sensitive, }, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitive) }) }) describe('data clauses', () => { it('does not leak string values in data clauses', () => { for (const sensitive of sensitiveValues) { const query = { arguments: { data: { field: sensitive } }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitive) } }) it('does not leak values in nested data operations', () => { const sensitiveEmail = 'private@secret.email' const sensitiveContent = 'confidential content here' const query = { arguments: { data: { email: sensitiveEmail, posts: { create: { content: sensitiveContent, }, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveEmail) expect(resultStr).not.toContain(sensitiveContent) }) }) describe('filter operators', () => { it('does not leak values in filter operators', () => { const operators = ['equals', 'contains', 'startsWith', 'endsWith', 'in', 'notIn', 'lt', 'gt', 'lte', 'gte'] for (const op of operators) { for (const sensitive of sensitiveValues) { const query = { arguments: { where: { field: { [op]: op === 'in' || op === 'notIn' ? [sensitive] : sensitive }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitive) } } }) it('does not leak values in search operator', () => { const sensitive = 'secret search term' const query = { arguments: { where: { content: { search: sensitive }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitive) }) it('does not leak values in array operators', () => { const sensitiveTag = 'secret-tag' const operators = ['has', 'hasEvery', 'hasSome'] for (const op of operators) { const query = { arguments: { where: { tags: { [op]: op === 'has' ? sensitiveTag : [sensitiveTag] }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveTag) } }) }) describe('nested relations', () => { it('does not leak values in nested relations', () => { const sensitive = 'super-secret-value' const query = { arguments: { where: { posts: { some: { content: { contains: sensitive }, }, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitive) }) it('does not leak values in deeply nested relation filters', () => { const sensitive1 = 'secret-author-name' const sensitive2 = 'secret-comment-content' const query = { arguments: { where: { posts: { some: { author: { name: sensitive1, }, comments: { every: { content: { contains: sensitive2 }, }, }, }, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitive1) expect(resultStr).not.toContain(sensitive2) }) }) describe('tagged values', () => { it('does not leak tagged DateTime values', () => { const dateValue = '2024-12-25T10:30:00.000Z' const query = { arguments: { where: { scheduledAt: { $type: 'DateTime', value: dateValue }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(dateValue) }) it('does not leak tagged Decimal values', () => { const decimalValue = '99999.99' const query = { arguments: { where: { salary: { $type: 'Decimal', value: decimalValue }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(decimalValue) }) it('does not leak tagged BigInt values', () => { const bigIntValue = '9007199254740993' const query = { arguments: { where: { bigNumber: { $type: 'BigInt', value: bigIntValue }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(bigIntValue) }) it('does not leak tagged Bytes values', () => { const bytesValue = 'U2VjcmV0RGF0YQ==' const query = { arguments: { where: { fileData: { $type: 'Bytes', value: bytesValue }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(bytesValue) }) it('does not leak tagged Json values', () => { const jsonValue = '{"secret":"dummy-api-key-NOTREAL"}' const query = { arguments: { where: { metadata: { $type: 'Json', value: jsonValue }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(jsonValue) expect(resultStr).not.toContain('dummy-api-key-NOTREAL') }) it('does not leak tagged Enum values', () => { const enumValue = 'SECRET_STATUS' const query = { arguments: { where: { status: { $type: 'Enum', value: enumValue }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(enumValue) }) }) describe('logical operators', () => { it('does not leak values in deeply nested AND/OR conditions', () => { const sensitiveEmail = 'private@secret.com' const sensitivePassword = 'p@ssw0rd!' const sensitiveRecoveryCode = 'backup-123' const query = { arguments: { where: { AND: [ { email: sensitiveEmail }, { OR: [{ password: sensitivePassword }, { recoveryCode: sensitiveRecoveryCode }], }, ], }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveEmail) expect(resultStr).not.toContain(sensitivePassword) expect(resultStr).not.toContain(sensitiveRecoveryCode) }) it('does not leak values in NOT conditions', () => { const sensitive = 'excluded-secret' const query = { arguments: { where: { NOT: { secretField: sensitive, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitive) }) }) describe('cursor pagination', () => { it('does not leak cursor ID values', () => { const sensitiveId = 'user_abc123xyz' const query = { arguments: { cursor: { id: sensitiveId }, take: 10, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveId) }) it('does not leak compound cursor values', () => { const sensitiveEmail = 'cursor@secret.com' const sensitiveTenant = 'tenant-secret-123' const query = { arguments: { cursor: { email_tenantId: { email: sensitiveEmail, tenantId: sensitiveTenant, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveEmail) expect(resultStr).not.toContain(sensitiveTenant) }) }) describe('JSON operators', () => { it('does not leak values in JSON path queries', () => { const sensitivePath1 = 'secretField' const sensitivePath2 = 'nestedSecret' const sensitiveValue = 'secret-json-value' const query = { arguments: { where: { metadata: { path: [sensitivePath1, sensitivePath2], equals: sensitiveValue, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitivePath1) expect(resultStr).not.toContain(sensitivePath2) expect(resultStr).not.toContain(sensitiveValue) }) it('does not leak values in JSON string operators', () => { const sensitiveSearch = 'confidential-content' const operators = ['string_contains', 'string_starts_with', 'string_ends_with'] for (const op of operators) { const query = { arguments: { where: { jsonField: { [op]: sensitiveSearch }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveSearch) } }) it('does not leak values in JSON array operators', () => { const sensitiveElement = 'secret-array-element' const operators = ['array_contains', 'array_starts_with', 'array_ends_with'] for (const op of operators) { const query = { arguments: { where: { jsonField: { [op]: sensitiveElement }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveElement) } }) }) describe('connect/disconnect operations', () => { it('does not leak IDs in connect operations', () => { const sensitiveId1 = 'secret-id-111' const sensitiveId2 = 'secret-id-222' const query = { arguments: { data: { tags: { connect: [{ id: sensitiveId1 }, { id: sensitiveId2 }], }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveId1) expect(resultStr).not.toContain(sensitiveId2) }) it('does not leak IDs in connectOrCreate operations', () => { const sensitiveId = 'secret-connect-or-create-id' const sensitiveEmail = 'connectorcreate@secret.com' const query = { arguments: { data: { profile: { connectOrCreate: { where: { id: sensitiveId }, create: { email: sensitiveEmail }, }, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) expect(resultStr).not.toContain(sensitiveId) expect(resultStr).not.toContain(sensitiveEmail) }) }) describe('combined sensitive data', () => { it('does not leak any values in a complex real-world query', () => { const sensitiveData = { email: 'john.doe@privatecompany.com', password: 'SuperSecret123!', ssn: '123-45-6789', creditCard: '4111-1111-1111-1111', apiKey: 'placeholder_api_key_NOTREAL', internalId: 'usr_2024_confidential', salary: '150000.00', birthDate: '1990-05-15T00:00:00Z', } const query = { arguments: { where: { OR: [{ email: sensitiveData.email }, { ssn: sensitiveData.ssn }], AND: [ { apiKey: { equals: sensitiveData.apiKey } }, { payments: { some: { cardNumber: { contains: sensitiveData.creditCard }, }, }, }, ], }, data: { password: sensitiveData.password, salary: { $type: 'Decimal', value: sensitiveData.salary }, birthDate: { $type: 'DateTime', value: sensitiveData.birthDate }, profile: { update: { internalId: sensitiveData.internalId, }, }, }, }, selection: { $scalars: true }, } const result = parameterizeQuery(query) const resultStr = JSON.stringify(result) for (const [key, value] of Object.entries(sensitiveData)) { expect(resultStr, `should not contain ${key}`).not.toContain(value) } }) }) })

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/prisma/prisma'

If you have feedback or need assistance with the MCP directory API, please join our Discord server