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:
copilot-swe-agent[bot]
2026-01-08 14:37:41 +00:00
parent 4c2bc9e09c
commit a326196b51
4 changed files with 850 additions and 59 deletions

View File

@@ -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 {