Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ on:
tags:
- 'v*' # Run on any tag that starts with v (e.g., v1.0.0)

permissions:
contents: read

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
pull_request:
branches: [ main ]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1339,7 +1339,7 @@ QueryLeaf provides seamless integration with MongoDB Atlas through familiar SQL
-- QueryLeaf Atlas connection and management
-- Connect to Atlas cluster with connection string
CONNECT TO atlas_cluster WITH (
connection_string = 'mongodb+srv://username:password@cluster.mongodb.net/database',
connection_string = 'mongodb+srv://<user>:<pass>@cluster.mongodb.net/database',
read_preference = 'secondaryPreferred',
write_concern = 'majority',
max_pool_size = 50,
Expand Down
2 changes: 1 addition & 1 deletion docs/blog/posts/mongodb-gridfs-file-management-sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ const fs = require('fs');
const crypto = require('crypto');
const path = require('path');

const client = new MongoClient('mongodb+srv://username:password@cluster.mongodb.net');
const client = new MongoClient(process.env.MONGODB_URI);
const db = client.db('file_storage_platform');

// Advanced GridFS file management system
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ const { MongoClient } = require('mongodb');
const { OpenAI } = require('openai');
const tf = require('@tensorflow/tfjs-node');

const client = new MongoClient('mongodb+srv://username:password@cluster.mongodb.net');
const client = new MongoClient(process.env.MONGODB_URI);
const db = client.db('advanced_ai_search_platform');

// Advanced AI-powered search and recommendation engine
Expand Down
2 changes: 2 additions & 0 deletions packages/lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,7 @@ export {
DummyMongoClient,
};

export { redactSql } from './redact';

// Re-export interfaces
export * from './interfaces';
8 changes: 7 additions & 1 deletion packages/lib/src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { From, Parser as NodeSqlParser } from 'node-sql-parser';
import { SqlParser, SqlStatement } from './interfaces';
import { redactSql } from './redact';
import debug from 'debug';

const log = debug('queryleaf:parser');
const rawLog = debug('queryleaf:parser');

function log(message: string, ...args: unknown[]): void {
const safeArgs = args.map((arg) => (typeof arg === 'string' ? redactSql(arg) : arg));
rawLog(message, ...safeArgs);
}

// Custom PostgreSQL mode with extensions to support our syntax needs
const CUSTOM_DIALECT = {
Expand Down
10 changes: 10 additions & 0 deletions packages/lib/src/redact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SQL fed to the parser or the postgres protocol handler may carry literal
// credentials (CREATE USER ... PASSWORD '...', ALTER USER ... IDENTIFIED BY ...).
// Mask them before they reach debug logs.
export function redactSql(sql: string | undefined): string {
if (!sql) return '';
return sql.replace(
/\b(PASSWORD|IDENTIFIED\s+BY|IDENTIFIED\s+WITH\s+\S+\s+AS)\s+('([^']|'')*'|"([^"]|"")*"|\S+)/gi,
'$1 ***'
);
}
37 changes: 26 additions & 11 deletions packages/postgres-server/src/protocol-handler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { Socket } from 'net';
import { QueryLeaf } from '@queryleaf/lib';
import { QueryLeaf, redactSql } from '@queryleaf/lib';
import { Transform } from 'stream';
import { randomInt } from 'crypto';
import debugLib from 'debug';
import { MongoClient, Document } from 'mongodb';

const debug = debugLib('queryleaf:pg-server:protocol');

// Strip password / query payloads from a parsed client message before logging.
function redactMessage(message: { type?: string; string?: string; query?: string }): object {
const { string: _str, query, ...rest } = message;
const safe: Record<string, unknown> = { ...rest };
if (message.type === 'password') {
safe.string = '***';
} else if (message.type === 'query' || message.type === 'parse') {
safe.query = redactSql(query ?? message.string);
} else if (message.string !== undefined) {
safe.string = message.string;
}
return safe;
}

// Simplified protocol implementation for demo purposes
interface BackendMessage {
// Buffer containing the formatted message ready to send
Expand Down Expand Up @@ -564,7 +579,7 @@ export class ProtocolHandler {
this.buffer = this.buffer.subarray(message.length);
debug(`Message processed, remaining buffer length: ${this.buffer.length}`);

debug('Received message type:', messageType, message);
debug('Received message type:', messageType, redactMessage(message));

// Handle the message
this.handleMessage(message.type as MessageName, message);
Expand Down Expand Up @@ -634,7 +649,7 @@ export class ProtocolHandler {
* Handle a startup message
*/
private handleStartup(message: ClientMessage): void {
debug('Startup message:', message);
debug('Startup message received');

// Extract user and database from parameters
if (message.parameters) {
Expand Down Expand Up @@ -795,7 +810,7 @@ export class ProtocolHandler {
* Handle a query message
*/
private async handleQuery(queryString: string): Promise<void> {
debug('Query message:', queryString);
debug('Query message:', redactSql(queryString));

if (!this.authenticated) {
debug('Not authenticated, rejecting query');
Expand Down Expand Up @@ -912,7 +927,7 @@ export class ProtocolHandler {
private handleParse(message: ClientMessage): void {
const { name, query } = message;

debug('Parse message:', name, query);
debug('Parse message:', name, redactSql(query));

try {
// Store the prepared statement for later
Expand All @@ -931,7 +946,7 @@ export class ProtocolHandler {
* Handle a bind message
*/
private handleBind(message: ClientMessage): void {
debug('Bind message:', message);
debug('Bind message:', redactMessage(message));

// In a real implementation, you would bind parameters to a prepared statement
// For now, just acknowledge the bind
Expand All @@ -942,7 +957,7 @@ export class ProtocolHandler {
* Handle a describe message
*/
private handleDescribe(message: ClientMessage): void {
debug('Describe message:', message);
debug('Describe message:', redactMessage(message));

const type = message.string;
const name = message.name;
Expand All @@ -969,7 +984,7 @@ export class ProtocolHandler {
* Handle an execute message
*/
private async handleExecute(message: ClientMessage): Promise<void> {
debug('Execute message:', message);
debug('Execute message:', redactMessage(message));

const { portal, maxRows } = message;

Expand Down Expand Up @@ -1060,9 +1075,9 @@ export class ProtocolHandler {
* Send backend key data
*/
private sendBackendKeyData(): void {
// Generate random process ID and key
const processId = Math.floor(Math.random() * 10000);
const secretKey = Math.floor(Math.random() * 1000000);
// The secret key authenticates CancelRequest messages, so it must be unguessable.
const processId = randomInt(1, 0x7fffffff);
const secretKey = randomInt(1, 0x7fffffff);

this.sendMessage(this.serializer.backendKeyData(processId, secretKey));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class MockQueryLeaf {
constructor(public client: any, public dbName: string) {}

execute(query: string) {
log(`Mock executing query: ${query}`);
log(`Mock executing query of length ${query.length}`);
Comment thread
rathboma marked this conversation as resolved.
Dismissed
if (query.includes('users')) {
return [
{ name: 'John', age: 30 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MockQueryLeaf {
constructor(public client: any, public dbName: string) {}

execute(query: string): any[] {
log(`Mock executing query: ${query}`);
log(`Mock executing query of length ${query.length}`);
Comment thread
rathboma marked this conversation as resolved.
Dismissed
if (query.includes('test')) {
return [{ test: 'success' }];
}
Expand Down
4 changes: 3 additions & 1 deletion packages/postgres-server/tests/unit/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { Socket } from 'net';

// Mock the QueryLeaf import
jest.mock('@queryleaf/lib', () => {
const actual = jest.requireActual('@queryleaf/lib');
return {
...actual,
QueryLeaf: class MockQueryLeaf {
constructor(public client: any, public dbName: string) {}

async execute(sql: string): Promise<any> {
if (sql === 'SELECT * FROM users') {
return [
Expand Down
Loading