mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-24 13:55:00 +00:00
Complete security redesign: secure SQL templates, safe component expressions, type safety
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
@@ -105,61 +105,212 @@ function MyComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Database Queries - Use Drizzle ORM
|
||||
## 2. Secure SQL Templates with Drizzle ORM
|
||||
|
||||
**IMPORTANT SECURITY NOTE:** This project previously included SQL template strings in `features.json`, but they have been removed due to SQL injection risks.
|
||||
SQL templates now use a **type-safe, injection-proof design** with parameter validation and Drizzle ORM patterns.
|
||||
|
||||
### Why SQL Templates Were Removed
|
||||
### Security Features
|
||||
|
||||
SQL templates with string interpolation (e.g., `{{tableName}}`) are inherently unsafe because they:
|
||||
1. Allow SQL injection if user input is not properly sanitized
|
||||
2. Encourage dangerous string concatenation patterns
|
||||
3. Bypass type-safe query builders
|
||||
1. **Parameter Type Validation** - All parameters have defined types and validation rules
|
||||
2. **SQL Identifier Escaping** - Uses `sql.identifier()` for table/column names
|
||||
3. **Parameterized Queries** - Uses `$1, $2` placeholders instead of string interpolation
|
||||
4. **Enum Validation** - Data types and index types validated against allowed values
|
||||
5. **No String Interpolation** - Templates provide Drizzle patterns, not raw SQL strings
|
||||
|
||||
### Use Drizzle ORM Instead
|
||||
### Parameter Types
|
||||
|
||||
All database queries should use Drizzle ORM, which provides:
|
||||
- **Type safety** - Compile-time validation of queries
|
||||
- **SQL injection prevention** - Automatic parameterization
|
||||
- **Better performance** - Query optimization
|
||||
- **Cleaner code** - Fluent API
|
||||
```json
|
||||
{
|
||||
"sqlTemplates": {
|
||||
"parameterTypes": {
|
||||
"tableName": {
|
||||
"type": "identifier",
|
||||
"validation": "^[a-zA-Z_][a-zA-Z0-9_]{0,62}$",
|
||||
"sanitize": "identifier"
|
||||
},
|
||||
"dataType": {
|
||||
"type": "enum",
|
||||
"allowedValues": ["INTEGER", "VARCHAR", "TEXT", "BOOLEAN"],
|
||||
"sanitize": "enum"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"min": 1,
|
||||
"max": 10000,
|
||||
"default": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Correct Way to Query Database
|
||||
### Query Templates
|
||||
|
||||
```json
|
||||
{
|
||||
"sqlTemplates": {
|
||||
"queries": {
|
||||
"tables": {
|
||||
"dropTable": {
|
||||
"description": "Drop a table using sql.identifier",
|
||||
"method": "drizzle.execute",
|
||||
"parameters": {
|
||||
"tableName": "tableName"
|
||||
},
|
||||
"drizzlePattern": {
|
||||
"type": "identifier",
|
||||
"example": "sql`DROP TABLE IF EXISTS ${sql.identifier([tableName])} CASCADE`"
|
||||
},
|
||||
"securityNotes": "Uses sql.identifier() for safe identifier escaping"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using SQL Templates Securely
|
||||
|
||||
```typescript
|
||||
import { db } from '@/utils/db';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import {
|
||||
getSqlQueryTemplate,
|
||||
validateSqlTemplateParams
|
||||
} from '@/utils/featureConfig';
|
||||
|
||||
// ✅ GOOD: Using Drizzle ORM with parameterized queries
|
||||
async function getTableData(tableName: string) {
|
||||
// Use sql.identifier for table/column names
|
||||
const result = await db.execute(sql`
|
||||
SELECT * FROM ${sql.identifier([tableName])}
|
||||
LIMIT 100
|
||||
`);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
// ✅ GOOD: Using Drizzle ORM query builder
|
||||
async function insertRecord(table: any, data: Record<string, any>) {
|
||||
const result = await db.insert(table).values(data).returning();
|
||||
return result[0];
|
||||
}
|
||||
|
||||
// ❌ BAD: String concatenation (DO NOT USE)
|
||||
async function unsafeQuery(tableName: string) {
|
||||
// This is vulnerable to SQL injection!
|
||||
const query = `SELECT * FROM "${tableName}"`;
|
||||
return await db.execute(sql.raw(query));
|
||||
async function dropTable(tableName: string) {
|
||||
// Get the template
|
||||
const template = getSqlQueryTemplate('tables', 'dropTable');
|
||||
|
||||
// Validate parameters - this prevents SQL injection
|
||||
const validation = validateSqlTemplateParams('tables', 'dropTable', {
|
||||
tableName: tableName
|
||||
});
|
||||
|
||||
if (!validation.valid) {
|
||||
throw new Error(`Invalid parameters: ${validation.errors?.join(', ')}`);
|
||||
}
|
||||
|
||||
// Use the sanitized values with Drizzle's safe methods
|
||||
const { tableName: safeTableName } = validation.sanitized!;
|
||||
|
||||
// Execute using Drizzle's sql.identifier() - safe from SQL injection
|
||||
const result = await db.execute(
|
||||
sql`DROP TABLE IF EXISTS ${sql.identifier([safeTableName])} CASCADE`
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### See Also
|
||||
- [Drizzle ORM Documentation](https://orm.drizzle.team/docs/overview)
|
||||
- [SQL Injection Prevention Guide](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)
|
||||
- API route examples in `src/app/api/admin/` directory
|
||||
### Security Comparison
|
||||
|
||||
## 3. Playwright Playbooks
|
||||
```typescript
|
||||
// ❌ OLD INSECURE WAY (REMOVED):
|
||||
// const query = `DROP TABLE "${tableName}"`; // SQL injection risk!
|
||||
// await db.execute(sql.raw(query));
|
||||
|
||||
// ✅ NEW SECURE WAY:
|
||||
// 1. Validate parameter against regex pattern
|
||||
const validation = validateSqlTemplateParams('tables', 'dropTable', { tableName });
|
||||
if (!validation.valid) throw new Error('Invalid table name');
|
||||
|
||||
// 2. Use Drizzle's sql.identifier() for automatic escaping
|
||||
await db.execute(sql`DROP TABLE ${sql.identifier([validation.sanitized.tableName])}`);
|
||||
```
|
||||
|
||||
### Why This is Secure
|
||||
|
||||
1. **Regex Validation**: Table names must match `^[a-zA-Z_][a-zA-Z0-9_]{0,62}$`
|
||||
- Prevents: `users; DROP TABLE users--`
|
||||
- Allows: `users`, `user_accounts`, `_temp_table`
|
||||
|
||||
2. **sql.identifier()**: Drizzle properly escapes identifiers
|
||||
- Handles special characters safely
|
||||
- Prevents SQL injection in table/column names
|
||||
|
||||
3. **Parameterized Queries**: Uses `$1, $2` placeholders
|
||||
- Database driver handles escaping
|
||||
- No string concatenation
|
||||
|
||||
4. **Type Validation**: Enums and integers validated before use
|
||||
- Data types checked against whitelist
|
||||
- Numeric values validated for range
|
||||
|
||||
## 3. Secure Component Templates
|
||||
|
||||
Component tree templates now use **safe property access** instead of `new Function()`.
|
||||
|
||||
### Security Features
|
||||
|
||||
1. **No Code Execution** - Replaced `new Function()` with safe property accessor
|
||||
2. **Whitelist Operations** - Only allowed operators: `===`, `!==`, `>`, `<`, `>=`, `<=`, `&&`, `||`
|
||||
3. **Property Path Validation** - Validates `^[a-zA-Z_$][a-zA-Z0-9_$.]*$`
|
||||
4. **Safe Math Operations** - Limited to: `abs`, `ceil`, `floor`, `round`, `max`, `min`
|
||||
|
||||
### Template Expressions
|
||||
|
||||
```json
|
||||
{
|
||||
"component": "Typography",
|
||||
"props": {
|
||||
"text": "{{user.name}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Patterns
|
||||
|
||||
```typescript
|
||||
// ✅ SAFE - Simple property access
|
||||
"{{user.name}}"
|
||||
"{{user.profile.email}}"
|
||||
|
||||
// ✅ SAFE - Comparisons with whitelisted operators
|
||||
"condition": "isAdmin === true"
|
||||
"condition": "count > 10"
|
||||
"condition": "status === 'active' && role === 'editor'"
|
||||
|
||||
// ✅ SAFE - Ternary expressions
|
||||
"{{isActive ? 'Active' : 'Inactive'}}"
|
||||
|
||||
// ✅ SAFE - Math operations (whitelisted)
|
||||
"{{Math.round(price)}}"
|
||||
"{{Math.max(a, b)}}"
|
||||
|
||||
// ❌ BLOCKED - Arbitrary code execution
|
||||
"{{require('fs').readFileSync('/etc/passwd')}}" // Validation fails
|
||||
"{{eval('malicious code')}}" // Validation fails
|
||||
"{{process.exit(1)}}" // Validation fails
|
||||
```
|
||||
|
||||
### Security Comparison
|
||||
|
||||
```typescript
|
||||
// ❌ OLD INSECURE WAY (REMOVED):
|
||||
// const func = new Function('user', `return ${expression}`);
|
||||
// return func(user); // Can execute ANY JavaScript code!
|
||||
|
||||
// ✅ NEW SECURE WAY:
|
||||
function safeGetProperty(obj: any, path: string): any {
|
||||
// Only allows: letters, numbers, dots, underscores
|
||||
if (!/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(path)) {
|
||||
return undefined; // Reject invalid paths
|
||||
}
|
||||
|
||||
// Safe property traversal
|
||||
const parts = path.split('.');
|
||||
let current = obj;
|
||||
for (const part of parts) {
|
||||
if (current == null) return undefined;
|
||||
current = current[part];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Playwright Playbooks
|
||||
|
||||
Define E2E test scenarios in JSON.
|
||||
|
||||
@@ -282,6 +433,38 @@ const tree = getComponentTree('TableManagerTab');
|
||||
const allTrees = getAllComponentTrees();
|
||||
```
|
||||
|
||||
### SQL Templates (Secure)
|
||||
```typescript
|
||||
import {
|
||||
getSqlQueryTemplate,
|
||||
getSqlParameterType,
|
||||
validateSqlTemplateParams,
|
||||
validateSqlParameter,
|
||||
getAllSqlTemplates,
|
||||
getSqlTemplatesByCategory,
|
||||
} from '@/utils/featureConfig';
|
||||
|
||||
// Get a query template
|
||||
const template = getSqlQueryTemplate('tables', 'dropTable');
|
||||
|
||||
// Get parameter type definition
|
||||
const paramType = getSqlParameterType('tableName');
|
||||
|
||||
// Validate a single parameter
|
||||
const validation = validateSqlParameter('tableName', 'users');
|
||||
if (!validation.valid) {
|
||||
console.error(validation.error);
|
||||
}
|
||||
|
||||
// Validate all parameters for a template
|
||||
const result = validateSqlTemplateParams('tables', 'dropTable', {
|
||||
tableName: 'users'
|
||||
});
|
||||
if (result.valid) {
|
||||
const safeParams = result.sanitized; // Use these sanitized values
|
||||
}
|
||||
```
|
||||
|
||||
### Playwright Playbooks
|
||||
```typescript
|
||||
import {
|
||||
|
||||
Reference in New Issue
Block a user