Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@ docs/.contentlayer
docs/.content-collections

# database instantiation
**/postgres_data/
**/postgres_data/
.qodo
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
FROM node:20-alpine
FROM node:18-alpine

# Install build dependencies
RUN apk add --no-cache python3 make g++

# Set working directory
WORKDIR /app
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/src/generated/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,14 @@ export const availableBlocks = [
"google_drive",
"google_sheets",
"jina",
"mongodb",
"mysql",
"notion",
"openai",
"pinecone",
"postgresql",
"reddit",
"redis",
"router",
"serper",
"slack",
Expand Down
31 changes: 0 additions & 31 deletions sim/.env.example

This file was deleted.

163 changes: 163 additions & 0 deletions sim/app/api/databases/mysql/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { createPool, PoolOptions, ResultSetHeader, RowDataPacket } from 'mysql2/promise'
import { NextResponse } from 'next/server'
import { getMySQLConfig } from '@/config/database'

export async function POST(request: Request) {
try {
const body = await request.json()
console.log('[MySQL API] Received request:', {
...body,
password: body.password ? '[REDACTED]' : undefined,
connection: body.connection ? {
...body.connection,
password: body.connection.password ? '[REDACTED]' : undefined
} : undefined
})

// Get connection parameters from either:
// 1. Request body
// 2. Environment variables
let connection = body.connection || {
host: body.host,
port: body.port,
user: body.user || body.username,
password: body.password,
database: body.database,
ssl: body.ssl === 'true'
}

// If no connection details provided in the request, try to get from environment
if (!connection.host || !connection.user || !connection.password || !connection.database) {
const envConfig = getMySQLConfig()
if (envConfig) {
connection = {
...connection,
host: connection.host || envConfig.host,
port: connection.port || envConfig.port,
user: connection.user || envConfig.user,
password: connection.password || envConfig.password,
database: connection.database || envConfig.database,
ssl: connection.ssl !== undefined ? connection.ssl : envConfig.ssl
}
}
}

// Check for required connection parameters
const hasRequiredParams = {
hasConnection: !!connection,
hasHost: !!connection?.host,
hasUsername: !!(connection?.user || connection?.username),
hasPassword: !!connection?.password,
hasDatabase: !!connection?.database
}

console.log('[MySQL API] Connection parameters check:', hasRequiredParams)

if (!hasRequiredParams.hasConnection || !hasRequiredParams.hasHost ||
!hasRequiredParams.hasUsername || !hasRequiredParams.hasPassword ||
!hasRequiredParams.hasDatabase) {
console.log('[MySQL API] Missing required connection parameters:', hasRequiredParams)
throw new Error('Missing required connection parameters')
}

// Create MySQL connection pool
console.log('[MySQL API] Creating connection pool with config:', {
...connection,
password: '[REDACTED]'
})

const poolConfig: PoolOptions = {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSL configuration with 'rejectUnauthorized: false' is insecure and vulnerable to man-in-the-middle attacks

host: connection.host,
port: parseInt(connection.port || '3306'),
user: connection.user || connection.username,
password: connection.password,
database: connection.database,
ssl: connection.ssl === 'true' ? { rejectUnauthorized: false } : undefined
}

console.log('[MySQL API] Attempting to create connection pool with config:', {
...poolConfig,
password: '[REDACTED]'
})

const pool = createPool(poolConfig)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connection pool is never closed after operations complete, which may lead to connection leaks


// Test the connection with a simple query
console.log('[MySQL API] Testing connection with SELECT 1')
try {
await pool.query('SELECT 1')
console.log('[MySQL API] Connection test successful')
} catch (testError: any) {
console.error('[MySQL API] Connection test failed:', testError)
throw testError
}

// Execute query based on operation
const { operation, query, params, options } = body
console.log('[MySQL API] Executing query:', { operation, query, params, options })

let result
switch (operation?.toLowerCase()) {
case 'select':
result = await pool.query<RowDataPacket[]>(query, params)
return NextResponse.json({ rows: result[0], fields: result[1] })

case 'insert':
case 'update':
case 'delete':
result = await pool.query<ResultSetHeader>(query, params)
return NextResponse.json({ affectedRows: result[0].affectedRows })

case 'execute':
result = await pool.query(query, params)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No input validation or SQL injection protection for direct query execution

return NextResponse.json({ result })

default:
throw new Error(`Unsupported operation: ${operation}`)
}
} catch (error: any) {
console.error('[MySQL API] Error:', error)
console.error('[MySQL API] Error details:', {
name: error.name,
message: error.message,
code: error.code,
errno: error.errno,
sqlState: error.sqlState,
sqlMessage: error.sqlMessage
})

// Handle specific MySQL errors
if (error.code === 'ER_ACCESS_DENIED_ERROR') {
return NextResponse.json(
{ error: 'Access denied. Please check your username and password.' },
{ status: 401 }
)
}

if (error.code === 'ECONNREFUSED') {
return NextResponse.json(
{ error: 'Could not connect to MySQL server. Please check if the server is running and accessible.' },
{ status: 503 }
)
}

if (error.code === 'ER_BAD_DB_ERROR') {
return NextResponse.json(
{ error: 'Database does not exist.' },
{ status: 404 }
)
}

if (error.code === 'ER_NO_SUCH_TABLE') {
return NextResponse.json(
{ error: 'Table does not exist.' },
{ status: 404 }
)
}

return NextResponse.json(
{ error: error.message || 'An error occurred while executing the query.' },
{ status: 500 }
)
}
}
94 changes: 94 additions & 0 deletions sim/app/api/databases/postgresql/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Pool, FieldDef } from 'pg'
import { NextResponse } from 'next/server'
import { getPostgreSQLConfig } from '@/config/database'

export async function POST(request: Request) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing input validation for operation type and query structure

try {
console.log('[PostgreSQL API] Received request')

const params = await request.json()
console.log('[PostgreSQL API] Parsed request params:', {
...params,
connection: {
...params.connection,
password: '[REDACTED]'
}
})

let { connection, operation, query, params: queryParams, options } = params

// If no connection details provided in the request, try to get from environment
if (!connection || !connection.host || !connection.username || !connection.password || !connection.database) {
const envConfig = getPostgreSQLConfig()
if (envConfig) {
connection = {
...connection,
host: connection?.host || envConfig.host,
port: connection?.port || envConfig.port,
username: connection?.username || envConfig.user,
password: connection?.password || envConfig.password,
database: connection?.database || envConfig.database,
ssl: connection?.ssl !== undefined ? connection.ssl : envConfig.ssl
}
}
}

// Check for required connection parameters
if (!connection || !connection.host || !connection.username || !connection.password || !connection.database) {
console.error('[PostgreSQL API] Missing required connection parameters')
throw new Error('Missing required connection parameters')
}

// Create connection pool
const { host, port, username, password, database, ssl } = connection
console.log('[PostgreSQL API] Creating connection pool:', { host, port, database, ssl })

const pool = new Pool({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating and immediately closing a connection pool per request negates pooling benefits

host,
port,
user: username,
password,
database,
ssl: ssl ? { rejectUnauthorized: false } : undefined
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Insecure SSL configuration disables certificate validation

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rule violated: Prevent Default Empty Values for Required Security Parameters

  SSL certificate validation is disabled when SSL is enabled

})

// Test connection
try {
console.log('[PostgreSQL API] Testing connection...')
await pool.query('SELECT 1')
console.log('[PostgreSQL API] Connection test successful')
} catch (error) {
console.error('[PostgreSQL API] Connection test failed:', error)
throw error
}

// Execute query
console.log('[PostgreSQL API] Executing query:', query)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging raw SQL queries may expose sensitive data

const result = await pool.query(query, queryParams || [])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL injection vulnerability: executing user-provided query without validation

console.log('[PostgreSQL API] Query result:', {
rowCount: result.rowCount,
fieldCount: result.fields?.length,
rows: result.rows
})

// Close pool
await pool.end()
console.log('[PostgreSQL API] Connection pool closed')

return NextResponse.json({
rows: result.rows,
rowCount: result.rowCount,
fields: result.fields?.map((field: FieldDef) => ({
name: field.name,
type: field.dataTypeID
})) || []
})

} catch (error) {
console.error('[PostgreSQL API] Error:', error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Unknown error occurred' },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic error handling may expose sensitive database information

{ status: 500 }
)
}
}
20 changes: 4 additions & 16 deletions sim/app/api/webhooks/trigger/[path]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ describe('Webhook Trigger API Route', () => {
// Mock all dependencies
mockExecutionDependencies()

// Mock Redis for duplicate detection
vi.doMock('@/lib/redis', () => ({
// Mock deduplication system
vi.doMock('@/lib/deduplication', () => ({
hasProcessedMessage: vi.fn().mockResolvedValue(false),
markMessageAsProcessed: vi.fn().mockResolvedValue(true),
closeRedisConnection: vi.fn().mockResolvedValue(undefined),
markMessageAsProcessed: vi.fn().mockResolvedValue(undefined),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent return type between test mocks. In one mock, markMessageAsProcessed returns undefined while in other tests it returns true.

}))

// Mock database with webhook data
Expand Down Expand Up @@ -252,16 +251,6 @@ describe('Webhook Trigger API Route', () => {
// Mock the path param
const params = Promise.resolve({ path: 'test-path' })

// Import Redis mocks
const hasProcessedMessageMock = vi.fn().mockResolvedValue(false)
const markMessageAsProcessedMock = vi.fn().mockResolvedValue(true)

vi.doMock('@/lib/redis', () => ({
hasProcessedMessage: hasProcessedMessageMock,
markMessageAsProcessed: markMessageAsProcessedMock,
closeRedisConnection: vi.fn().mockResolvedValue(undefined),
}))

// Import the handler after mocks are set up
const { POST } = await import('./route')

Expand Down Expand Up @@ -323,10 +312,9 @@ describe('Webhook Trigger API Route', () => {
const markMessageAsProcessedMock = vi.fn().mockResolvedValue(true)

// Mock hasProcessedMessage to return true (duplicate)
vi.doMock('@/lib/redis', () => ({
vi.doMock('@/lib/deduplication', () => ({
hasProcessedMessage: hasProcessedMessageMock,
markMessageAsProcessed: markMessageAsProcessedMock,
closeRedisConnection: vi.fn().mockResolvedValue(undefined),
}))

// Create executor mock to verify it's not called
Expand Down
Loading