Files
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

114 lines
3.4 KiB
Markdown

# Database Security (SQL Injection Prevention)
This codebase uses Drizzle ORM with SQLite. All database queries must use parameterized SQL so user-controlled input never changes query structure.
## Quick Rules
- Use Drizzle's `sql` tagged template literals for dynamic values.
- Use `sql.join()` for dynamic lists such as `IN (...)` clauses.
- Pass `SQL<unknown>` fragments between functions, not strings.
- Do not build queries with `sql.raw()` or string interpolation.
- Prefer `json_each()` for user-selected JSON keys. When `json_extract()` is appropriate, bind a path built with the vetted `buildSafeJsonPath` helper in `src/models/eval.ts`.
## Required Pattern: Use `sql` Template Strings
Use parameterized queries for single values:
```typescript
import { sql } from 'drizzle-orm';
const query = sql`SELECT * FROM eval_results WHERE eval_id = ${evalId}`;
```
Use parameterized queries for multiple values:
```typescript
import { sql } from 'drizzle-orm';
const query = sql`
SELECT * FROM eval_results
WHERE eval_id = ${evalId} AND success = ${1}
`;
```
Use `sql.join()` for dynamic `IN (...)` lists:
```typescript
import { sql } from 'drizzle-orm';
const ids = ['id1', 'id2', 'id3'];
const query = sql`SELECT * FROM evals WHERE id IN (${sql.join(ids, sql`, `)})`;
```
## Forbidden Pattern: Raw SQL with Dynamic Content
Do not interpolate user-controlled values into raw SQL:
```typescript
const query = sql.raw(`SELECT * FROM eval_results WHERE eval_id = '${evalId}'`);
```
Do not build SQL conditions with string concatenation:
```typescript
import { sql } from 'drizzle-orm';
const whereClause = `eval_id = '${evalId}'`;
const query = sql.raw(`SELECT * FROM eval_results WHERE ${whereClause}`);
```
## JSON Paths in SQLite
SQLite's `json_extract()` accepts its JSON path as a bound value. Do not use `sql.raw()` to splice user-controlled JSON paths into SQL. Prefer `json_each()` when filtering by dynamic keys, because the key can be compared as a normal parameter:
```typescript
import { sql } from 'drizzle-orm';
const query = sql`
SELECT *
FROM eval_results
WHERE EXISTS (
SELECT 1
FROM json_each(metadata)
WHERE json_each.key = ${field} AND json_each.value = ${value}
)
`;
```
If you construct a JSON path for `json_extract()`, use `buildSafeJsonPath` from `src/models/eval.ts`, which escapes backslashes and double quotes for JSON path syntax and returns a value to bind:
```typescript
import { sql } from 'drizzle-orm';
import { buildSafeJsonPath } from '../../src/models/eval';
const jsonPath = buildSafeJsonPath(userField);
const query = sql`
SELECT * FROM eval_results
WHERE json_extract(metadata, ${jsonPath}) = ${value}
`;
```
If you need a new JSON-path helper, match the guarantees in `buildSafeJsonPath` and keep the implementation audited in one shared utility.
## Passing SQL Fragments Between Functions
When building complex queries, pass `SQL<unknown>` fragments instead of strings:
```typescript
import { type SQL, sql } from 'drizzle-orm';
function queryWithFilter(whereSql: SQL<unknown>): Promise<Result[]> {
const query = sql`SELECT * FROM eval_results WHERE ${whereSql}`;
return db.all(query);
}
const filter = sql`eval_id = ${evalId} AND success = ${1}`;
const results = await queryWithFilter(filter);
```
## Key Files with Database Queries
- `src/models/eval.ts` - Main eval queries and JSON-path helper
- `src/util/calculateFilteredMetrics.ts` - Metrics aggregation queries
- `src/database/index.ts` - Database connection