sqlcipher-mcp-server 1.0.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,58 +1,147 @@
1
- /**
2
- * Validation Utilities
3
- * Input validation and sanitization functions
4
- */
5
-
6
- import { getDatabasePath } from '../config/environment.js';
7
-
8
- /**
9
- * Validate that arguments is a valid object
10
- * @param {any} args - Arguments to validate
11
- * @throws {Error} If args is not a valid object
12
- */
13
- export function validateArguments(args) {
14
- if (!args || typeof args !== 'object') {
15
- throw new Error('Invalid arguments: arguments must be an object');
16
- }
17
- }
18
-
19
- /**
20
- * Validate query parameter
21
- * @param {any} query - Query to validate
22
- * @throws {Error} If query is invalid
23
- */
24
- export function validateQuery(query) {
25
- if (!query || typeof query !== 'string') {
26
- throw new Error('query is required and must be a string');
27
- }
28
- }
29
-
30
- /**
31
- * Validate and resolve database path
32
- * Uses provided path or falls back to environment variable
33
- * @param {string|undefined} providedPath - Database path from arguments
34
- * @returns {string} Resolved database path
35
- * @throws {Error} If no valid path is available
36
- */
37
- export function resolveDatabasePath(providedPath) {
38
- const dbPath = providedPath || getDatabasePath();
39
-
40
- if (!dbPath || typeof dbPath !== 'string') {
41
- throw new Error(
42
- 'database_path is required. Provide it as a parameter or set SQLCIPHER_DATABASE_PATH environment variable.'
43
- );
44
- }
45
-
46
- return dbPath;
47
- }
48
-
49
- /**
50
- * Validate database path parameter for HTTP requests
51
- * @param {any} database_path - Database path to validate
52
- * @throws {Error} If database_path is invalid
53
- */
54
- export function validateDatabasePath(database_path) {
55
- if (!database_path || typeof database_path !== 'string') {
56
- throw new Error('database_path is required and must be a string');
57
- }
58
- }
1
+ /**
2
+ * Validation Utilities
3
+ * Input validation and sanitization functions
4
+ */
5
+
6
+ import { getDatabasePath } from '../config/environment.js';
7
+
8
+ /**
9
+ * Validate that arguments is a valid object
10
+ * @param {any} args - Arguments to validate
11
+ * @throws {Error} If args is not a valid object
12
+ */
13
+ export function validateArguments(args) {
14
+ if (!args || typeof args !== 'object') {
15
+ throw new Error('Invalid arguments: arguments must be an object');
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Validate query parameter
21
+ * @param {any} query - Query to validate
22
+ * @throws {Error} If query is invalid
23
+ */
24
+ export function validateQuery(query) {
25
+ if (!query || typeof query !== 'string') {
26
+ throw new Error('query is required and must be a string');
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Validate and resolve database path
32
+ * Uses provided path or falls back to environment variable
33
+ * @param {string|undefined} providedPath - Database path from arguments
34
+ * @returns {string} Resolved database path
35
+ * @throws {Error} If no valid path is available
36
+ */
37
+ export function resolveDatabasePath(providedPath) {
38
+ const dbPath = providedPath || getDatabasePath();
39
+
40
+ if (!dbPath || typeof dbPath !== 'string') {
41
+ throw new Error(
42
+ 'database_path is required. Provide it as a parameter or set SQLCIPHER_DATABASE_PATH environment variable.'
43
+ );
44
+ }
45
+
46
+ return dbPath;
47
+ }
48
+
49
+ /**
50
+ * Validate database path parameter for HTTP requests
51
+ * @param {any} database_path - Database path to validate
52
+ * @throws {Error} If database_path is invalid
53
+ */
54
+ export function validateDatabasePath(database_path) {
55
+ if (!database_path || typeof database_path !== 'string') {
56
+ throw new Error('database_path is required and must be a string');
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Validate table name parameter
62
+ * @param {any} tableName - Table name to validate
63
+ * @throws {Error} If tableName is invalid
64
+ */
65
+ export function validateTableName(tableName) {
66
+ if (!tableName) {
67
+ throw new Error('table_name is required');
68
+ }
69
+
70
+ if (typeof tableName !== 'string' && !Array.isArray(tableName)) {
71
+ throw new Error('table_name must be a string or array of strings');
72
+ }
73
+
74
+ if (Array.isArray(tableName)) {
75
+ if (tableName.length === 0) {
76
+ throw new Error('table_name array cannot be empty');
77
+ }
78
+ tableName.forEach((name, index) => {
79
+ if (typeof name !== 'string') {
80
+ throw new Error(`table_name[${index}] must be a string`);
81
+ }
82
+ });
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Validate column name parameter
88
+ * @param {any} columnName - Column name to validate
89
+ * @throws {Error} If columnName is invalid
90
+ */
91
+ export function validateColumnName(columnName) {
92
+ if (!columnName) {
93
+ throw new Error('column_name is required');
94
+ }
95
+
96
+ if (typeof columnName !== 'string' && !Array.isArray(columnName)) {
97
+ throw new Error('column_name must be a string or array of strings');
98
+ }
99
+
100
+ if (Array.isArray(columnName)) {
101
+ if (columnName.length === 0) {
102
+ throw new Error('column_name array cannot be empty');
103
+ }
104
+ columnName.forEach((name, index) => {
105
+ if (typeof name !== 'string') {
106
+ throw new Error(`column_name[${index}] must be a string`);
107
+ }
108
+ });
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Validate pattern parameter for search operations
114
+ * @param {any} pattern - Pattern to validate
115
+ * @throws {Error} If pattern is invalid
116
+ */
117
+ export function validatePattern(pattern) {
118
+ if (!pattern || typeof pattern !== 'string') {
119
+ throw new Error('pattern is required and must be a string');
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Validate numeric parameter
125
+ * @param {any} value - Value to validate
126
+ * @param {string} paramName - Parameter name for error messages
127
+ * @param {number} min - Minimum allowed value
128
+ * @param {number} max - Maximum allowed value
129
+ * @returns {number} Validated number
130
+ * @throws {Error} If value is invalid
131
+ */
132
+ export function validateNumericParameter(value, paramName, min = 0, max = Number.MAX_SAFE_INTEGER) {
133
+ if (value === undefined || value === null) {
134
+ return undefined;
135
+ }
136
+
137
+ const num = Number(value);
138
+ if (isNaN(num)) {
139
+ throw new Error(`${paramName} must be a number`);
140
+ }
141
+
142
+ if (num < min || num > max) {
143
+ throw new Error(`${paramName} must be between ${min} and ${max}`);
144
+ }
145
+
146
+ return num;
147
+ }
package/lib/database.js DELETED
@@ -1,216 +0,0 @@
1
- import sqlcipher from '@journeyapps/sqlcipher';
2
- import fs from 'fs';
3
-
4
- // Extract Database from the sqlcipher module object
5
- const Database = sqlcipher.Database;
6
-
7
- /**
8
- * Connects to a SQLite database (encrypted or unencrypted)
9
- * Supports both SQLCipher-encrypted and plain SQLite databases
10
- *
11
- * @param {string} dbPath - Path to the database file
12
- * @param {string} [password] - Optional database password (for encrypted databases)
13
- * @returns {Promise<Database>} Database connection instance
14
- * @throws {Error} If database file doesn't exist or connection fails
15
- */
16
- export function connectDatabase(dbPath, password) {
17
- return new Promise((resolve, reject) => {
18
- // Validate database path exists
19
- if (!fs.existsSync(dbPath)) {
20
- return reject(new Error(`Database file not found: ${dbPath}`));
21
- }
22
-
23
- let db = null;
24
- // Open database connection with callback
25
- db = new Database(dbPath, (err) => {
26
- if (err) {
27
- return reject(new Error(`Failed to open database: ${err.message}`));
28
- }
29
-
30
- // If no password provided, treat as unencrypted SQLite database
31
- if (!password || password.trim() === '') {
32
- // Verify the database is accessible by running a simple query
33
- db.get('SELECT 1', (getErr, row) => {
34
- if (getErr) {
35
- db.close((closeErr) => {
36
- // Ignore close errors
37
- });
38
- return reject(new Error(`Failed to verify database: ${getErr.message}`));
39
- }
40
- resolve(db);
41
- });
42
- return;
43
- }
44
-
45
- // Password provided - treat as encrypted SQLCipher database
46
- // Explicitly set SQLCipher 3 compatibility mode
47
- // This ensures SQLCipher 3 defaults are used:
48
- // - Page size: 1024 bytes
49
- // - PBKDF2 iterations: 64,000
50
- // - KDF algorithm: PBKDF2-HMAC-SHA1
51
- // - HMAC algorithm: HMAC-SHA1
52
- db.exec('PRAGMA cipher_compatibility = 3', (compatErr) => {
53
- if (compatErr) {
54
- db.close((closeErr) => {
55
- // Ignore close errors
56
- });
57
- return reject(new Error(`Failed to set SQLCipher 3 compatibility: ${compatErr.message}`));
58
- }
59
-
60
- // Set SQLCipher 3 default encryption settings
61
- // PRAGMA key sets the encryption key using SQLCipher 3 defaults
62
- // PRAGMA key does NOT support parameterized queries, so we must embed the password directly
63
- // Escape single quotes in password for SQL (double them) and escape backslashes
64
- const escapedPassword = password.replace(/\\/g, '\\\\').replace(/'/g, "''");
65
-
66
- // Use db.exec() with callback for PRAGMA key
67
- db.exec(`PRAGMA key = '${escapedPassword}'`, (execErr) => {
68
- if (execErr) {
69
- db.close((closeErr) => {
70
- // Ignore close errors
71
- });
72
- return reject(new Error(`Failed to set encryption key: ${execErr.message}`));
73
- }
74
-
75
- // Verify the database is accessible by running a simple query
76
- // This will throw an error if the password is incorrect
77
- db.get('SELECT 1', (getErr, row) => {
78
- if (getErr) {
79
- db.close((closeErr) => {
80
- // Ignore close errors
81
- });
82
-
83
- if (getErr.message.includes('file is not a database') ||
84
- getErr.message.includes('malformed database') ||
85
- getErr.code === 'SQLITE_NOTADB') {
86
- return reject(new Error('Invalid password or database is corrupted'));
87
- }
88
- return reject(new Error(`Failed to verify database: ${getErr.message}`));
89
- }
90
-
91
- resolve(db);
92
- });
93
- });
94
- });
95
- });
96
- });
97
- }
98
-
99
- /**
100
- * Validates that a SQL query is a SELECT query (read-only)
101
- *
102
- * @param {string} query - SQL query string
103
- * @returns {boolean} True if query is a SELECT query
104
- * @throws {Error} If query is not a SELECT query
105
- */
106
- function validateSelectQuery(query) {
107
- if (!query || typeof query !== 'string') {
108
- throw new Error('Query must be a non-empty string');
109
- }
110
-
111
- // Trim and normalize whitespace
112
- const normalizedQuery = query.trim().replace(/\s+/g, ' ');
113
-
114
- // Check if query starts with SELECT (case-insensitive)
115
- if (!normalizedQuery.match(/^SELECT\s+/i)) {
116
- throw new Error('Only SELECT queries are allowed (read-only mode)');
117
- }
118
-
119
- // Additional check: ensure no DDL or DML keywords are present
120
- const forbiddenKeywords = [
121
- 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER',
122
- 'TRUNCATE', 'REPLACE', 'PRAGMA'
123
- ];
124
-
125
- const upperQuery = normalizedQuery.toUpperCase();
126
- for (const keyword of forbiddenKeywords) {
127
- // Check for keyword followed by space or semicolon (to avoid false positives)
128
- const regex = new RegExp(`\\b${keyword}\\s+`, 'i');
129
- if (regex.test(normalizedQuery) && keyword !== 'SELECT') {
130
- throw new Error(`Query contains forbidden keyword: ${keyword}. Only SELECT queries are allowed.`);
131
- }
132
- }
133
-
134
- return true;
135
- }
136
-
137
- /**
138
- * Executes a SELECT query on the database and returns results
139
- *
140
- * @param {Database} db - Database connection instance
141
- * @param {string} query - SQL SELECT query to execute
142
- * @returns {Promise<Object>} Query results with columns, rows, and rowCount
143
- * @throws {Error} If query is invalid or execution fails
144
- */
145
- export function executeQuery(db, query) {
146
- return new Promise((resolve, reject) => {
147
- // Validate query is a SELECT query
148
- try {
149
- validateSelectQuery(query);
150
- } catch (validationError) {
151
- return reject(validationError);
152
- }
153
-
154
- // Prepare and execute the query with callback
155
- const statement = db.prepare(query, (prepareErr) => {
156
- if (prepareErr) {
157
- return reject(new Error(`Query preparation failed: ${prepareErr.message}`));
158
- }
159
-
160
- // Execute query with callback - statement.all() requires a callback
161
- statement.all((allErr, rows) => {
162
- if (allErr) {
163
- statement.finalize();
164
- if (allErr.message.includes('no such table')) {
165
- return reject(new Error(`Table not found: ${allErr.message}`));
166
- } else if (allErr.message.includes('no such column')) {
167
- return reject(new Error(`Column not found: ${allErr.message}`));
168
- } else if (allErr.message.includes('syntax error')) {
169
- return reject(new Error(`SQL syntax error: ${allErr.message}`));
170
- }
171
- return reject(new Error(`Query execution failed: ${allErr.message}`));
172
- }
173
-
174
- // Get column names from the first row
175
- let columns = [];
176
- if (rows && rows.length > 0) {
177
- columns = Object.keys(rows[0]);
178
- }
179
-
180
- // Finalize the statement
181
- statement.finalize();
182
-
183
- resolve({
184
- columns: columns,
185
- rows: rows || [],
186
- rowCount: rows ? rows.length : 0
187
- });
188
- });
189
- });
190
- });
191
- }
192
-
193
- /**
194
- * Closes a database connection
195
- * Ensures all statements are finalized before closing
196
- *
197
- * @param {Database} db - Database connection instance
198
- */
199
- export function closeConnection(db) {
200
- if (!db || typeof db.close !== 'function') {
201
- return;
202
- }
203
-
204
- try {
205
- // Close with callback to handle any errors gracefully
206
- db.close((err) => {
207
- if (err) {
208
- // Log but don't throw - closing should be best effort
209
- console.error('Error closing database connection:', err.message);
210
- }
211
- });
212
- } catch (error) {
213
- // Log but don't throw - closing should be best effort
214
- console.error('Error closing database connection:', error.message);
215
- }
216
- }