noormme 1.0.0 → 1.0.2

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.
Files changed (137) hide show
  1. package/LICENSE +89 -21
  2. package/README.md +195 -578
  3. package/dist/cjs/cli/commands/analyze.js +4 -1
  4. package/dist/cjs/cli/commands/generate.js +48 -8
  5. package/dist/cjs/cli/commands/init.js +54 -14
  6. package/dist/cjs/cli/commands/inspect.js +10 -3
  7. package/dist/cjs/cli/commands/migrate.js +38 -2
  8. package/dist/cjs/cli/commands/optimize.js +4 -1
  9. package/dist/cjs/cli/commands/status.js +41 -5
  10. package/dist/cjs/cli/commands/watch.js +4 -1
  11. package/dist/cjs/cli/index.js +4 -1
  12. package/dist/cjs/dialect/database-introspector.js +16 -21
  13. package/dist/cjs/dialect/sqlite/sqlite-introspector.js +13 -24
  14. package/dist/cjs/dynamic/dynamic.d.ts +20 -0
  15. package/dist/cjs/dynamic/dynamic.js +25 -0
  16. package/dist/cjs/edge-runtime/edge-config.d.ts +125 -0
  17. package/dist/cjs/edge-runtime/edge-config.js +323 -0
  18. package/dist/cjs/errors/NoormError.d.ts +27 -4
  19. package/dist/cjs/errors/NoormError.js +142 -21
  20. package/dist/cjs/logging/logger.d.ts +7 -2
  21. package/dist/cjs/logging/logger.js +21 -7
  22. package/dist/cjs/noormme.d.ts +12 -8
  23. package/dist/cjs/noormme.js +133 -61
  24. package/dist/cjs/operation-node/column-node.js +4 -0
  25. package/dist/cjs/operation-node/identifier-node.js +4 -0
  26. package/dist/cjs/operation-node/table-node.js +7 -0
  27. package/dist/cjs/parser/reference-parser.js +5 -0
  28. package/dist/cjs/parser/table-parser.js +2 -0
  29. package/dist/cjs/performance/index.d.ts +44 -0
  30. package/dist/cjs/performance/index.js +64 -0
  31. package/dist/cjs/performance/query-optimizer.d.ts +134 -0
  32. package/dist/cjs/performance/query-optimizer.js +391 -0
  33. package/dist/cjs/performance/services/cache-service.d.ts +177 -0
  34. package/dist/cjs/performance/services/cache-service.js +415 -0
  35. package/dist/cjs/performance/services/connection-factory.d.ts +198 -0
  36. package/dist/cjs/performance/services/connection-factory.js +498 -0
  37. package/dist/cjs/performance/services/metrics-collector.d.ts +162 -0
  38. package/dist/cjs/performance/services/metrics-collector.js +406 -0
  39. package/dist/cjs/performance/utils/query-parser.d.ts +123 -0
  40. package/dist/cjs/performance/utils/query-parser.js +295 -0
  41. package/dist/cjs/raw-builder/sql.d.ts +73 -26
  42. package/dist/cjs/raw-builder/sql.js +9 -0
  43. package/dist/cjs/repository/repository-factory.d.ts +10 -42
  44. package/dist/cjs/repository/repository-factory.js +276 -394
  45. package/dist/cjs/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
  46. package/dist/cjs/schema/core/factories/discovery-factory.js +5 -5
  47. package/dist/cjs/schema/core/utils/name-generator.js +34 -2
  48. package/dist/cjs/schema/core/utils/type-mapper.d.ts +19 -14
  49. package/dist/cjs/schema/core/utils/type-mapper.js +4 -7
  50. package/dist/cjs/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
  51. package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
  52. package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
  53. package/dist/cjs/schema/test/dialect-capabilities.test.js +6 -0
  54. package/dist/cjs/schema/test/error-handling.test.js +52 -33
  55. package/dist/cjs/schema/test/integration.test.js +51 -5
  56. package/dist/cjs/schema/test/sqlite-discovery-coordinator.test.js +8 -4
  57. package/dist/cjs/sqlite-migration/index.js +35 -2
  58. package/dist/cjs/testing/test-utils.d.ts +5 -0
  59. package/dist/cjs/testing/test-utils.js +66 -6
  60. package/dist/cjs/types/index.d.ts +78 -13
  61. package/dist/cjs/types/index.js +23 -0
  62. package/dist/cjs/types/type-generator.d.ts +8 -0
  63. package/dist/cjs/types/type-generator.js +86 -17
  64. package/dist/cjs/util/safe-sql-helpers.d.ts +124 -0
  65. package/dist/cjs/util/safe-sql-helpers.js +221 -0
  66. package/dist/cjs/util/security-validator.d.ts +44 -0
  67. package/dist/cjs/util/security-validator.js +256 -0
  68. package/dist/cjs/util/security.d.ts +60 -0
  69. package/dist/cjs/util/security.js +137 -0
  70. package/dist/cjs/watch/schema-watcher.js +26 -7
  71. package/dist/esm/cli/commands/generate.js +10 -6
  72. package/dist/esm/cli/commands/init.js +15 -11
  73. package/dist/esm/cli/commands/inspect.js +6 -2
  74. package/dist/esm/cli/commands/status.js +3 -3
  75. package/dist/esm/dialect/database-introspector.js +16 -21
  76. package/dist/esm/dialect/sqlite/sqlite-introspector.js +13 -24
  77. package/dist/esm/dynamic/dynamic.d.ts +20 -0
  78. package/dist/esm/dynamic/dynamic.js +25 -0
  79. package/dist/esm/edge-runtime/edge-config.d.ts +125 -0
  80. package/dist/esm/edge-runtime/edge-config.js +281 -0
  81. package/dist/esm/errors/NoormError.d.ts +27 -4
  82. package/dist/esm/errors/NoormError.js +134 -18
  83. package/dist/esm/logging/logger.d.ts +7 -2
  84. package/dist/esm/logging/logger.js +21 -7
  85. package/dist/esm/noormme.d.ts +12 -8
  86. package/dist/esm/noormme.js +130 -61
  87. package/dist/esm/operation-node/column-node.js +4 -0
  88. package/dist/esm/operation-node/identifier-node.js +4 -0
  89. package/dist/esm/operation-node/table-node.js +7 -0
  90. package/dist/esm/parser/reference-parser.js +5 -0
  91. package/dist/esm/parser/table-parser.js +2 -0
  92. package/dist/esm/performance/index.d.ts +44 -0
  93. package/dist/esm/performance/index.js +48 -0
  94. package/dist/esm/performance/query-optimizer.d.ts +134 -0
  95. package/dist/esm/performance/query-optimizer.js +387 -0
  96. package/dist/esm/performance/services/cache-service.d.ts +177 -0
  97. package/dist/esm/performance/services/cache-service.js +410 -0
  98. package/dist/esm/performance/services/connection-factory.d.ts +198 -0
  99. package/dist/esm/performance/services/connection-factory.js +493 -0
  100. package/dist/esm/performance/services/metrics-collector.d.ts +162 -0
  101. package/dist/esm/performance/services/metrics-collector.js +402 -0
  102. package/dist/esm/performance/utils/query-parser.d.ts +123 -0
  103. package/dist/esm/performance/utils/query-parser.js +292 -0
  104. package/dist/esm/raw-builder/sql.d.ts +73 -26
  105. package/dist/esm/raw-builder/sql.js +9 -0
  106. package/dist/esm/repository/repository-factory.d.ts +10 -42
  107. package/dist/esm/repository/repository-factory.js +277 -395
  108. package/dist/esm/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
  109. package/dist/esm/schema/core/factories/discovery-factory.js +5 -5
  110. package/dist/esm/schema/core/utils/name-generator.js +34 -2
  111. package/dist/esm/schema/core/utils/type-mapper.d.ts +19 -14
  112. package/dist/esm/schema/core/utils/type-mapper.js +4 -7
  113. package/dist/esm/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
  114. package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
  115. package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
  116. package/dist/esm/schema/test/dialect-capabilities.test.js +6 -0
  117. package/dist/esm/schema/test/error-handling.test.js +52 -33
  118. package/dist/esm/schema/test/integration.test.js +18 -5
  119. package/dist/esm/schema/test/sqlite-discovery-coordinator.test.js +8 -4
  120. package/dist/esm/testing/test-utils.d.ts +5 -0
  121. package/dist/esm/testing/test-utils.js +66 -6
  122. package/dist/esm/types/index.d.ts +78 -13
  123. package/dist/esm/types/index.js +22 -1
  124. package/dist/esm/types/type-generator.d.ts +8 -0
  125. package/dist/esm/types/type-generator.js +86 -17
  126. package/dist/esm/util/safe-sql-helpers.d.ts +124 -0
  127. package/dist/esm/util/safe-sql-helpers.js +209 -0
  128. package/dist/esm/util/security-validator.d.ts +44 -0
  129. package/dist/esm/util/security-validator.js +246 -0
  130. package/dist/esm/util/security.d.ts +60 -0
  131. package/dist/esm/util/security.js +114 -0
  132. package/dist/esm/watch/schema-watcher.js +26 -7
  133. package/package.json +1 -1
  134. package/dist/cjs/performance/query-analyzer.d.ts +0 -89
  135. package/dist/cjs/performance/query-analyzer.js +0 -263
  136. package/dist/esm/performance/query-analyzer.d.ts +0 -89
  137. package/dist/esm/performance/query-analyzer.js +0 -260
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ /**
3
+ * Safe SQL helper utilities
4
+ * Provides secure alternatives to dangerous sql.raw() and sql.lit() methods
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SafeSQLExamples = exports.SafeSQLKeywords = void 0;
8
+ exports.safeOrderDirection = safeOrderDirection;
9
+ exports.safeLimit = safeLimit;
10
+ exports.safeOffset = safeOffset;
11
+ exports.safeKeyword = safeKeyword;
12
+ exports.safeLockMode = safeLockMode;
13
+ exports.safeOrderBy = safeOrderBy;
14
+ exports.safeBoolean = safeBoolean;
15
+ exports.safeCaseStatement = safeCaseStatement;
16
+ exports.validateEnum = validateEnum;
17
+ exports.validateNumericRange = validateNumericRange;
18
+ const sql_js_1 = require("../raw-builder/sql.js");
19
+ /**
20
+ * Safely creates a SQL ORDER BY direction clause
21
+ * Only allows 'ASC' or 'DESC'
22
+ */
23
+ function safeOrderDirection(direction) {
24
+ const normalized = direction.toUpperCase().trim();
25
+ if (normalized !== 'ASC' && normalized !== 'DESC') {
26
+ throw new Error(`Invalid order direction: "${direction}". Only "ASC" and "DESC" are allowed.`);
27
+ }
28
+ return sql_js_1.sql.raw(normalized);
29
+ }
30
+ /**
31
+ * Safely creates a SQL LIMIT clause
32
+ * Validates that the limit is a positive integer
33
+ */
34
+ function safeLimit(limit) {
35
+ const numLimit = typeof limit === 'string' ? parseInt(limit, 10) : limit;
36
+ if (!Number.isInteger(numLimit) || numLimit <= 0 || numLimit > 10000) {
37
+ throw new Error(`Invalid limit: "${limit}". Must be a positive integer between 1 and 10000.`);
38
+ }
39
+ return sql_js_1.sql.raw(numLimit.toString());
40
+ }
41
+ /**
42
+ * Safely creates a SQL OFFSET clause
43
+ * Validates that the offset is a non-negative integer
44
+ */
45
+ function safeOffset(offset) {
46
+ const numOffset = typeof offset === 'string' ? parseInt(offset, 10) : offset;
47
+ if (!Number.isInteger(numOffset) || numOffset < 0 || numOffset > 1000000) {
48
+ throw new Error(`Invalid offset: "${offset}". Must be a non-negative integer between 0 and 1000000.`);
49
+ }
50
+ return sql_js_1.sql.raw(numOffset.toString());
51
+ }
52
+ /**
53
+ * Safely creates SQL keywords from a whitelist
54
+ * Useful for database-specific syntax that must be hardcoded
55
+ */
56
+ function safeKeyword(keyword, allowedKeywords) {
57
+ const normalized = keyword.toUpperCase().trim();
58
+ const allowedNormalized = allowedKeywords.map(k => k.toUpperCase().trim());
59
+ if (!allowedNormalized.includes(normalized)) {
60
+ throw new Error(`Invalid keyword: "${keyword}". Allowed keywords: ${allowedKeywords.join(', ')}`);
61
+ }
62
+ return sql_js_1.sql.raw(normalized);
63
+ }
64
+ /**
65
+ * Type-safe enum for common SQL keywords
66
+ */
67
+ exports.SafeSQLKeywords = {
68
+ /**
69
+ * Lock modes for SELECT statements
70
+ */
71
+ LockMode: {
72
+ FOR_UPDATE: 'FOR UPDATE',
73
+ FOR_SHARE: 'FOR SHARE',
74
+ FOR_NO_KEY_UPDATE: 'FOR NO KEY UPDATE',
75
+ FOR_KEY_SHARE: 'FOR KEY SHARE',
76
+ },
77
+ /**
78
+ * Join types
79
+ */
80
+ JoinType: {
81
+ INNER: 'INNER',
82
+ LEFT: 'LEFT',
83
+ RIGHT: 'RIGHT',
84
+ FULL: 'FULL',
85
+ CROSS: 'CROSS',
86
+ },
87
+ /**
88
+ * Set operations
89
+ */
90
+ SetOperation: {
91
+ UNION: 'UNION',
92
+ UNION_ALL: 'UNION ALL',
93
+ INTERSECT: 'INTERSECT',
94
+ EXCEPT: 'EXCEPT',
95
+ },
96
+ /**
97
+ * SQLite-specific pragmas (use with extreme caution)
98
+ */
99
+ SafePragma: {
100
+ OPTIMIZE: 'OPTIMIZE',
101
+ WAL_CHECKPOINT: 'WAL_CHECKPOINT',
102
+ },
103
+ };
104
+ /**
105
+ * Safely creates a lock mode clause for SELECT statements
106
+ */
107
+ function safeLockMode(mode) {
108
+ return sql_js_1.sql.raw(exports.SafeSQLKeywords.LockMode[mode]);
109
+ }
110
+ /**
111
+ * Creates a safe ORDER BY clause from validated columns
112
+ */
113
+ function safeOrderBy(orderBy, allowedColumns) {
114
+ if (orderBy.length === 0) {
115
+ throw new Error('At least one order by clause is required');
116
+ }
117
+ const clauses = orderBy.map(({ column, direction = 'ASC' }) => {
118
+ if (!allowedColumns.includes(column)) {
119
+ throw new Error(`Invalid order by column: "${column}". Allowed columns: ${allowedColumns.join(', ')}`);
120
+ }
121
+ const dir = direction.toUpperCase();
122
+ if (dir !== 'ASC' && dir !== 'DESC') {
123
+ throw new Error(`Invalid direction: "${direction}". Must be ASC or DESC`);
124
+ }
125
+ return `${column} ${dir}`;
126
+ });
127
+ return sql_js_1.sql.raw(clauses.join(', '));
128
+ }
129
+ /**
130
+ * Validates and creates a safe boolean value for SQL
131
+ */
132
+ function safeBoolean(value) {
133
+ let boolValue;
134
+ if (typeof value === 'boolean') {
135
+ boolValue = value;
136
+ }
137
+ else if (value === 'true' || value === '1' || value === 1) {
138
+ boolValue = true;
139
+ }
140
+ else if (value === 'false' || value === '0' || value === 0) {
141
+ boolValue = false;
142
+ }
143
+ else {
144
+ throw new Error(`Invalid boolean value: "${value}". Must be true, false, 1, 0, "true", or "false"`);
145
+ }
146
+ // Use parameterized value instead of raw SQL
147
+ return (0, sql_js_1.sql) `${boolValue}`;
148
+ }
149
+ function safeCaseStatement(cases, elseResult) {
150
+ if (cases.length === 0) {
151
+ throw new Error('At least one WHEN clause is required');
152
+ }
153
+ // Note: This function needs to be refactored to not use sql.raw()
154
+ // For now, we construct the CASE statement carefully
155
+ const whenClauses = cases
156
+ .map((c, i) => {
157
+ // Conditions must be RawBuilder instances to ensure they're safe
158
+ const conditionSql = c.condition.toOperationNode();
159
+ return `WHEN ${conditionSql} THEN ${c.result}`;
160
+ })
161
+ .join(' ');
162
+ return sql_js_1.sql.raw(`CASE ${whenClauses} ELSE ${elseResult} END`);
163
+ }
164
+ /**
165
+ * Example usage and best practices
166
+ */
167
+ exports.SafeSQLExamples = {
168
+ /**
169
+ * Example: Safe pagination with user input
170
+ */
171
+ safePagination: () => {
172
+ const userPage = '2'; // From request
173
+ const userLimit = '10'; // From request
174
+ const offset = (parseInt(userPage) - 1) * parseInt(userLimit);
175
+ return {
176
+ limit: safeLimit(userLimit),
177
+ offset: safeOffset(offset),
178
+ };
179
+ },
180
+ /**
181
+ * Example: Safe sorting with user input
182
+ */
183
+ safeSorting: (userSortColumn, userDirection) => {
184
+ const allowedColumns = ['name', 'email', 'created_at', 'updated_at'];
185
+ if (!allowedColumns.includes(userSortColumn)) {
186
+ throw new Error('Invalid sort column');
187
+ }
188
+ return {
189
+ column: sql_js_1.sql.ref(userSortColumn),
190
+ direction: safeOrderDirection(userDirection),
191
+ };
192
+ },
193
+ /**
194
+ * Example: Safe lock mode for transactions
195
+ */
196
+ safeTransactionLock: () => {
197
+ return safeLockMode('FOR_UPDATE');
198
+ },
199
+ };
200
+ /**
201
+ * Helper to validate that a value is from a specific enum
202
+ */
203
+ function validateEnum(value, enumValues, fieldName = 'value') {
204
+ if (!enumValues.includes(value)) {
205
+ throw new Error(`Invalid ${fieldName}: "${value}". Allowed values: ${enumValues.join(', ')}`);
206
+ }
207
+ return value;
208
+ }
209
+ /**
210
+ * Helper to validate numeric ranges
211
+ */
212
+ function validateNumericRange(value, min, max, fieldName = 'value') {
213
+ const num = typeof value === 'string' ? parseFloat(value) : value;
214
+ if (isNaN(num)) {
215
+ throw new Error(`Invalid ${fieldName}: "${value}". Must be a number`);
216
+ }
217
+ if (num < min || num > max) {
218
+ throw new Error(`Invalid ${fieldName}: ${num}. Must be between ${min} and ${max}`);
219
+ }
220
+ return num;
221
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Security validation utilities for NOORMME
3
+ * Provides input validation and sanitization to prevent injection attacks
4
+ */
5
+ /**
6
+ * Validates identifier names (table names, column names, schema names)
7
+ * to prevent SQL injection through dynamic identifiers
8
+ */
9
+ export declare function validateIdentifier(identifier: string, context?: string): void;
10
+ /**
11
+ * Validates table reference for dynamic table operations
12
+ */
13
+ export declare function validateTableReference(tableRef: string): void;
14
+ /**
15
+ * Validates column reference for dynamic column operations
16
+ */
17
+ export declare function validateColumnReference(columnRef: string): void;
18
+ /**
19
+ * Validates file paths to prevent path traversal attacks
20
+ */
21
+ export declare function validateFilePath(filePath: string, allowedExtensions?: string[]): void;
22
+ /**
23
+ * Sanitizes database path for CLI commands
24
+ */
25
+ export declare function sanitizeDatabasePath(dbPath: string): string;
26
+ /**
27
+ * Validates output directory for code generation
28
+ */
29
+ export declare function validateOutputDirectory(dirPath: string): void;
30
+ /**
31
+ * Validates migration name
32
+ */
33
+ export declare function validateMigrationName(name: string): void;
34
+ /**
35
+ * Rate limiting for security-sensitive operations
36
+ */
37
+ export declare class RateLimiter {
38
+ private maxAttempts;
39
+ private windowMs;
40
+ private attempts;
41
+ constructor(maxAttempts?: number, windowMs?: number);
42
+ checkLimit(key: string): void;
43
+ private cleanup;
44
+ }
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ /**
3
+ * Security validation utilities for NOORMME
4
+ * Provides input validation and sanitization to prevent injection attacks
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.RateLimiter = void 0;
8
+ exports.validateIdentifier = validateIdentifier;
9
+ exports.validateTableReference = validateTableReference;
10
+ exports.validateColumnReference = validateColumnReference;
11
+ exports.validateFilePath = validateFilePath;
12
+ exports.sanitizeDatabasePath = sanitizeDatabasePath;
13
+ exports.validateOutputDirectory = validateOutputDirectory;
14
+ exports.validateMigrationName = validateMigrationName;
15
+ /**
16
+ * Validates identifier names (table names, column names, schema names)
17
+ * to prevent SQL injection through dynamic identifiers
18
+ */
19
+ function validateIdentifier(identifier, context = 'identifier') {
20
+ if (typeof identifier !== 'string') {
21
+ throw new Error(`${context} must be a string`);
22
+ }
23
+ if (identifier.length === 0) {
24
+ throw new Error(`${context} cannot be empty`);
25
+ }
26
+ if (identifier.length > 255) {
27
+ throw new Error(`${context} exceeds maximum length of 255 characters`);
28
+ }
29
+ // Check for SQL injection patterns
30
+ const dangerousPatterns = [
31
+ /;/, // SQL statement separator
32
+ /--/, // SQL comment
33
+ /\/\*/, // Multi-line comment start
34
+ /\*\//, // Multi-line comment end
35
+ /\bUNION\b/i, // UNION injection
36
+ /\bSELECT\b.*\bFROM\b/i, // SELECT injection
37
+ /\bINSERT\b.*\bINTO\b/i, // INSERT injection
38
+ /\bUPDATE\b.*\bSET\b/i, // UPDATE injection
39
+ /\bDELETE\b.*\bFROM\b/i, // DELETE injection
40
+ /\bDROP\b/i, // DROP injection
41
+ /\bEXEC\b/i, // EXEC injection
42
+ /\bEXECUTE\b/i, // EXECUTE injection
43
+ /\bCREATE\b/i, // CREATE injection
44
+ /\bALTER\b/i, // ALTER injection
45
+ /\bTRUNCATE\b/i, // TRUNCATE injection
46
+ /['"`]/, // Quote characters
47
+ /\\/, // Backslash escape
48
+ /\x00/, // Null byte
49
+ ];
50
+ for (const pattern of dangerousPatterns) {
51
+ if (pattern.test(identifier)) {
52
+ throw new Error(`Invalid ${context}: "${identifier}" contains potentially dangerous characters or SQL keywords. ` +
53
+ `Identifiers must contain only alphanumeric characters, underscores, and dots (for schema.table references).`);
54
+ }
55
+ }
56
+ // Validate format: alphanumeric, underscore, and dots for schema.table.column
57
+ const validIdentifierPattern = /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
58
+ if (!validIdentifierPattern.test(identifier)) {
59
+ throw new Error(`Invalid ${context}: "${identifier}". ` +
60
+ `Identifiers must start with a letter or underscore, followed by alphanumeric characters or underscores. ` +
61
+ `Schema/table references can use dots (e.g., "schema.table" or "table.column").`);
62
+ }
63
+ // Additional check: prevent reserved SQLite keywords that could be dangerous
64
+ const reservedKeywords = [
65
+ 'PRAGMA', 'ATTACH', 'DETACH', 'VACUUM', 'ANALYZE', 'REINDEX'
66
+ ];
67
+ for (const keyword of reservedKeywords) {
68
+ if (identifier.toUpperCase() === keyword) {
69
+ throw new Error(`Invalid ${context}: "${identifier}" is a reserved SQLite keyword and cannot be used as an identifier`);
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Validates table reference for dynamic table operations
75
+ */
76
+ function validateTableReference(tableRef) {
77
+ validateIdentifier(tableRef, 'table reference');
78
+ // Additional validation for table references
79
+ const parts = tableRef.split('.');
80
+ if (parts.length > 2) {
81
+ throw new Error(`Invalid table reference: "${tableRef}". ` +
82
+ `Table references can have at most 2 parts (schema.table).`);
83
+ }
84
+ // Validate each part
85
+ parts.forEach((part, index) => {
86
+ const context = index === 0 ? (parts.length === 2 ? 'schema name' : 'table name') : 'table name';
87
+ validateIdentifier(part, context);
88
+ });
89
+ }
90
+ /**
91
+ * Validates column reference for dynamic column operations
92
+ */
93
+ function validateColumnReference(columnRef) {
94
+ validateIdentifier(columnRef, 'column reference');
95
+ // Column references can have: column, table.column, or schema.table.column
96
+ const parts = columnRef.split('.');
97
+ if (parts.length > 3) {
98
+ throw new Error(`Invalid column reference: "${columnRef}". ` +
99
+ `Column references can have at most 3 parts (schema.table.column).`);
100
+ }
101
+ // Validate each part
102
+ parts.forEach((part, index) => {
103
+ let context;
104
+ if (parts.length === 1) {
105
+ context = 'column name';
106
+ }
107
+ else if (parts.length === 2) {
108
+ context = index === 0 ? 'table name' : 'column name';
109
+ }
110
+ else {
111
+ context = index === 0 ? 'schema name' : (index === 1 ? 'table name' : 'column name');
112
+ }
113
+ validateIdentifier(part, context);
114
+ });
115
+ }
116
+ /**
117
+ * Validates file paths to prevent path traversal attacks
118
+ */
119
+ function validateFilePath(filePath, allowedExtensions) {
120
+ if (typeof filePath !== 'string') {
121
+ throw new Error('File path must be a string');
122
+ }
123
+ if (filePath.length === 0) {
124
+ throw new Error('File path cannot be empty');
125
+ }
126
+ // Check for path traversal patterns
127
+ const pathTraversalPatterns = [
128
+ /\.\./, // Parent directory reference
129
+ /~\//, // Home directory reference
130
+ /^\/+/, // Absolute path (starts with /)
131
+ /^[a-zA-Z]:\\/, // Windows absolute path (C:\)
132
+ /\\/, // Backslash (Windows path separator)
133
+ /\x00/, // Null byte injection
134
+ ];
135
+ for (const pattern of pathTraversalPatterns) {
136
+ if (pattern.test(filePath)) {
137
+ throw new Error(`Invalid file path: "${filePath}" contains path traversal or absolute path patterns. ` +
138
+ `Only relative paths within the current directory are allowed.`);
139
+ }
140
+ }
141
+ // Validate file extension if specified
142
+ if (allowedExtensions && allowedExtensions.length > 0) {
143
+ const hasValidExtension = allowedExtensions.some(ext => filePath.toLowerCase().endsWith(ext.toLowerCase()));
144
+ if (!hasValidExtension) {
145
+ throw new Error(`Invalid file extension for "${filePath}". ` +
146
+ `Allowed extensions: ${allowedExtensions.join(', ')}`);
147
+ }
148
+ }
149
+ // Check for suspicious file names
150
+ const suspiciousNames = [
151
+ /^\.+$/, // Only dots
152
+ /^\s*$/, // Only whitespace
153
+ /[<>:"|?*]/, // Windows forbidden characters
154
+ ];
155
+ for (const pattern of suspiciousNames) {
156
+ if (pattern.test(filePath)) {
157
+ throw new Error(`Invalid file path: "${filePath}" contains forbidden characters or is malformed`);
158
+ }
159
+ }
160
+ }
161
+ /**
162
+ * Sanitizes database path for CLI commands
163
+ */
164
+ function sanitizeDatabasePath(dbPath) {
165
+ validateFilePath(dbPath, ['.db', '.sqlite', '.sqlite3', '.db3']);
166
+ // Ensure the path doesn't escape current directory
167
+ if (dbPath.includes('..') || dbPath.startsWith('/') || /^[a-zA-Z]:/.test(dbPath)) {
168
+ throw new Error(`Database path "${dbPath}" must be a relative path within the current directory`);
169
+ }
170
+ return dbPath;
171
+ }
172
+ /**
173
+ * Validates output directory for code generation
174
+ */
175
+ function validateOutputDirectory(dirPath) {
176
+ if (typeof dirPath !== 'string') {
177
+ throw new Error('Output directory must be a string');
178
+ }
179
+ if (dirPath.length === 0) {
180
+ throw new Error('Output directory cannot be empty');
181
+ }
182
+ // Prevent path traversal
183
+ if (dirPath.includes('..')) {
184
+ throw new Error(`Output directory "${dirPath}" cannot contain parent directory references (..)`);
185
+ }
186
+ // Prevent absolute paths
187
+ if (dirPath.startsWith('/') || /^[a-zA-Z]:/.test(dirPath)) {
188
+ throw new Error(`Output directory "${dirPath}" must be a relative path`);
189
+ }
190
+ // Validate format
191
+ const validDirPattern = /^[a-zA-Z0-9_\-./]+$/;
192
+ if (!validDirPattern.test(dirPath)) {
193
+ throw new Error(`Invalid output directory: "${dirPath}". ` +
194
+ `Directory paths must contain only alphanumeric characters, underscores, hyphens, dots, and forward slashes.`);
195
+ }
196
+ }
197
+ /**
198
+ * Validates migration name
199
+ */
200
+ function validateMigrationName(name) {
201
+ if (typeof name !== 'string') {
202
+ throw new Error('Migration name must be a string');
203
+ }
204
+ if (name.length === 0) {
205
+ throw new Error('Migration name cannot be empty');
206
+ }
207
+ if (name.length > 100) {
208
+ throw new Error('Migration name exceeds maximum length of 100 characters');
209
+ }
210
+ // Allow only alphanumeric, underscores, and hyphens
211
+ const validNamePattern = /^[a-zA-Z0-9_\-]+$/;
212
+ if (!validNamePattern.test(name)) {
213
+ throw new Error(`Invalid migration name: "${name}". ` +
214
+ `Migration names must contain only alphanumeric characters, underscores, and hyphens.`);
215
+ }
216
+ }
217
+ /**
218
+ * Rate limiting for security-sensitive operations
219
+ */
220
+ class RateLimiter {
221
+ maxAttempts;
222
+ windowMs;
223
+ attempts = new Map();
224
+ constructor(maxAttempts = 10, windowMs = 60000 // 1 minute
225
+ ) {
226
+ this.maxAttempts = maxAttempts;
227
+ this.windowMs = windowMs;
228
+ }
229
+ checkLimit(key) {
230
+ const now = Date.now();
231
+ const attempts = this.attempts.get(key) || [];
232
+ // Remove old attempts outside the window
233
+ const recentAttempts = attempts.filter(time => now - time < this.windowMs);
234
+ if (recentAttempts.length >= this.maxAttempts) {
235
+ throw new Error(`Rate limit exceeded for ${key}. Please wait before trying again.`);
236
+ }
237
+ recentAttempts.push(now);
238
+ this.attempts.set(key, recentAttempts);
239
+ // Cleanup old entries periodically
240
+ if (this.attempts.size > 1000) {
241
+ this.cleanup(now);
242
+ }
243
+ }
244
+ cleanup(now) {
245
+ for (const [key, attempts] of this.attempts.entries()) {
246
+ const recentAttempts = attempts.filter(time => now - time < this.windowMs);
247
+ if (recentAttempts.length === 0) {
248
+ this.attempts.delete(key);
249
+ }
250
+ else {
251
+ this.attempts.set(key, recentAttempts);
252
+ }
253
+ }
254
+ }
255
+ }
256
+ exports.RateLimiter = RateLimiter;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Security utilities for NOORMME
3
+ * Export all security-related functions for easy import
4
+ */
5
+ export { validateIdentifier, validateTableReference, validateColumnReference, validateFilePath, sanitizeDatabasePath, validateOutputDirectory, validateMigrationName, RateLimiter, } from './security-validator.js';
6
+ export { safeOrderDirection, safeLimit, safeOffset, safeKeyword, safeLockMode, safeOrderBy, safeBoolean, safeCaseStatement, SafeSQLKeywords, SafeSQLExamples, validateEnum, validateNumericRange, type SafeOrderBy, type SafeCaseWhen, } from './safe-sql-helpers.js';
7
+ /**
8
+ * Security best practices and guidelines
9
+ */
10
+ export declare const SecurityGuidelines: {
11
+ /**
12
+ * NEVER use sql.raw() or sql.lit() with user input
13
+ */
14
+ readonly NEVER_USE_RAW_WITH_USER_INPUT: "Use safe alternatives or parameterized queries";
15
+ /**
16
+ * ALWAYS validate dynamic identifiers
17
+ */
18
+ readonly ALWAYS_VALIDATE_IDENTIFIERS: "Use validateColumnReference() or validateTableReference()";
19
+ /**
20
+ * ALWAYS use whitelists for user-controlled values
21
+ */
22
+ readonly ALWAYS_USE_WHITELISTS: "Define allowed values and validate against them";
23
+ /**
24
+ * ALWAYS sanitize file paths
25
+ */
26
+ readonly ALWAYS_SANITIZE_PATHS: "Use validateFilePath() or sanitizeDatabasePath()";
27
+ /**
28
+ * PREFER parameterized queries
29
+ */
30
+ readonly PREFER_PARAMETERIZED_QUERIES: "Use sql`${value}` instead of sql.raw()";
31
+ /**
32
+ * PREFER safe helpers over raw SQL
33
+ */
34
+ readonly PREFER_SAFE_HELPERS: "Use safeOrderDirection(), safeLimit(), etc.";
35
+ };
36
+ /**
37
+ * Quick security checklist for developers
38
+ */
39
+ export declare function securityChecklist(): string;
40
+ /**
41
+ * Common security patterns
42
+ */
43
+ export declare const SecurityPatterns: {
44
+ /**
45
+ * Safe column selection from user input
46
+ */
47
+ safeColumnSelection: (userColumn: string, allowedColumns: string[]) => string;
48
+ /**
49
+ * Safe table selection from user input
50
+ */
51
+ safeTableSelection: (userTable: string, allowedTables: string[]) => string;
52
+ /**
53
+ * Safe enum validation
54
+ */
55
+ safeEnumValue: <T extends string>(value: string, allowedValues: readonly T[]) => T;
56
+ /**
57
+ * Safe numeric range validation
58
+ */
59
+ safeNumericValue: (value: number | string, min: number, max: number) => number;
60
+ };