Commit 1f12ed1
fix: security hardening for SQL query API (#81)
* fix: replace string-based SQL injection in inject_block_filter with AST manipulation
The previous implementation used string position matching (finding WHERE,
ORDER BY, LIMIT keywords) and format! interpolation to splice block_num
filters into user-provided SQL. This was vulnerable to structural SQL
injection where crafted queries could exploit the naive keyword matching
(e.g. WHERE inside string literals, UNION bypasses).
Replace with sqlparser AST parsing and manipulation:
- Parse user SQL into AST, requiring a single simple SELECT statement
- Determine filter column from the FROM table (num for blocks, block_num
for others)
- Safely AND the block filter into the existing WHERE clause (or add one)
- Serialize modified AST back to SQL
Also:
- Reject UNION/INTERSECT/set operations in live mode (ambiguous filtering)
- Return Result<String, ApiError> instead of String for proper error handling
- Add Display impl for ApiError
- Add tests for UNION rejection, non-SELECT rejection, and WHERE keyword
in string literals
Amp-Thread-ID: https://ampcode.com/threads/T-019c3272-f632-763c-8078-504a90852a67
Co-authored-by: Amp <amp@ampcode.com>
* fix: add table allowlist to query validator and read-only API role
Replace the blocklist-only approach with a table allowlist so API users
can only query: blocks, txs, logs, receipts, token_holders,
token_balances, and CTE-defined tables.
Previously, users could query sync_state (internal), pg_tables (schema
enumeration), or any other table accessible to the tidx DB user.
Changes:
- Allowlist in validator: only permitted tables + CTE-defined names pass
- Block dblink function family (cross-database access)
- Add db/api_role.sql migration creating a tidx_api read-only role with
SELECT-only grants on indexed tables (defense-in-depth)
- Thread CTE names through all validate_* functions
- 6 new tests: sync_state rejected, pg_tables rejected, unknown table
rejected, CTE tables allowed, dblink blocked, analytics tables allowed
Amp-Thread-ID: https://ampcode.com/threads/T-019c3272-f632-763c-8078-504a90852a67
Co-authored-by: Amp <amp@ampcode.com>
* fix: close DoS, privilege escalation, and file read bypass vectors
Block three categories of attacks:
1. DoS via resource exhaustion:
- Reject WITH RECURSIVE (endless loop CTEs)
- Block generate_series() (billion-row generation)
- Block SELECT INTO (object creation)
2. Privilege escalation / validator bypass:
- Validate expressions inside VALUES rows (previously VALUES(pg_sleep(10))
bypassed the entire function blocklist)
- Reject TABLE statement (TABLE pg_shadow bypassed table allowlist)
- Validate GROUP BY, HAVING, JOIN ON expressions (could hide function calls)
- Walk IsNull/IsNotNull/IsTrue/IsFalse/Like expressions recursively
3. File read hardening:
- Block lo_get/lo_open/lo_close/loread/lo_creat/lo_create/lo_unlink/lo_put
- Block pg_file_read/pg_file_write/pg_file_rename/pg_file_unlink/pg_logdir_ls
- VALUES bypass closure prevents pg_read_file via VALUES(...)
11 new tests covering all vectors.
Amp-Thread-ID: https://ampcode.com/threads/T-019c3272-f632-763c-8078-504a90852a67
Co-authored-by: Amp <amp@ampcode.com>
* fix: replace function blocklist with allowlist and harden API role
Switch from a function blocklist (reject known-bad) to an allowlist
(permit known-good only). This eliminates the risk of missing dangerous
functions as PostgreSQL adds new ones.
Validator changes:
- ALLOWED_FUNCTIONS allowlist: ABI helpers, aggregates, scalars, string,
numeric, time, window functions, and type casting
- Reject ALL table functions (FROM func(...)) unconditionally
- Reject unsupported TableFactor variants (catch-all _ => Err)
- Remove is_dangerous_function() and is_dangerous_table_function()
API role hardening (db/api_role.sql):
- Deny-by-default: REVOKE ALL on tables, sequences, and functions
before granting specific access
- Add token_holders and token_balances to SELECT grants
- CONNECTION LIMIT 64
- statement_timeout = 30s, work_mem = 64MB, temp_file_limit = 256MB
5 new tests, all 147 lib tests passing.
Amp-Thread-ID: https://ampcode.com/threads/T-019c499a-f07e-73a9-9526-6c18fd511372
Co-authored-by: Amp <amp@ampcode.com>
* fix: reject-by-default expression validation, LIMIT/depth/size caps
Switch validate_expr to reject-by-default: only explicitly allowed
expression types are permitted (identifiers, literals, binary/unary ops,
CASE, CAST, BETWEEN, IN, LIKE, subqueries, SQL builtins like EXTRACT,
SUBSTRING, TRIM, etc). Unknown expression variants are rejected.
Query structure hardening:
- Reject FOR UPDATE/SHARE locking clauses
- Validate ORDER BY expressions through validate_expr
- Validate LIMIT/OFFSET: must be numeric literals, capped at 10,000
- Reject LIMIT BY (ClickHouse-specific)
- Subquery depth limit: max 4 levels of nesting
- Query size limit: max 64KB
- Validate window function OVER clause (PARTITION BY, ORDER BY)
Service layer:
- Replace string-based LIMIT detection (contains("LIMIT")) with
AST-based detection via append_limit_if_missing()
- Use HARD_LIMIT_MAX constant (10,000) across validator, service, and API
- API param clamping uses HARD_LIMIT_MAX instead of hardcoded 100,000
15 new tests, all 162 lib tests passing.
Amp-Thread-ID: https://ampcode.com/threads/T-019c499a-f07e-73a9-9526-6c18fd511372
Co-authored-by: Amp <amp@ampcode.com>
* fix: close FILTER clause bypass, LIMIT NULL, FETCH, negative limit
Fixes found during oracle review:
- Validate function FILTER (WHERE ...) clause: previously
COUNT(*) FILTER (WHERE pg_sleep(1) IS NOT NULL) bypassed
the function allowlist entirely
- Validate WITHIN GROUP (ORDER BY ...) expressions
- Reject LIMIT NULL (effectively means no limit, bypasses cap)
- Reject negative LIMIT/OFFSET values
- Reject FETCH clause (FETCH FIRST N ROWS ONLY bypasses LIMIT cap)
4 new tests, all 166 lib tests passing.
Amp-Thread-ID: https://ampcode.com/threads/T-019c499a-f07e-73a9-9526-6c18fd511372
Co-authored-by: Amp <amp@ampcode.com>
---------
Co-authored-by: Amp <amp@ampcode.com>1 parent daf8c45 commit 1f12ed1
File tree
7 files changed
+952
-229
lines changed- db
- src
- api
- db
- query
- service
- tests
7 files changed
+952
-229
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
257 | 257 | | |
258 | 258 | | |
259 | 259 | | |
260 | | - | |
| 260 | + | |
261 | 261 | | |
262 | 262 | | |
263 | 263 | | |
| |||
299 | 299 | | |
300 | 300 | | |
301 | 301 | | |
302 | | - | |
| 302 | + | |
303 | 303 | | |
304 | 304 | | |
305 | 305 | | |
| |||
397 | 397 | | |
398 | 398 | | |
399 | 399 | | |
400 | | - | |
| 400 | + | |
401 | 401 | | |
402 | 402 | | |
403 | 403 | | |
| |||
477 | 477 | | |
478 | 478 | | |
479 | 479 | | |
480 | | - | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
481 | 490 | | |
482 | 491 | | |
483 | 492 | | |
| |||
516 | 525 | | |
517 | 526 | | |
518 | 527 | | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
519 | 531 | | |
520 | | - | |
521 | | - | |
522 | | - | |
523 | | - | |
524 | | - | |
525 | | - | |
526 | | - | |
527 | | - | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
528 | 535 | | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
533 | | - | |
534 | | - | |
535 | | - | |
536 | | - | |
537 | | - | |
538 | | - | |
539 | | - | |
540 | | - | |
541 | | - | |
542 | | - | |
543 | | - | |
544 | | - | |
545 | | - | |
546 | | - | |
547 | | - | |
548 | | - | |
549 | | - | |
550 | | - | |
551 | | - | |
552 | | - | |
553 | | - | |
554 | | - | |
555 | | - | |
556 | | - | |
557 | | - | |
558 | | - | |
559 | | - | |
560 | | - | |
561 | | - | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
562 | 547 | | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
563 | 607 | | |
564 | 608 | | |
565 | 609 | | |
| |||
599 | 643 | | |
600 | 644 | | |
601 | 645 | | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
602 | 659 | | |
603 | 660 | | |
604 | 661 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
42 | 45 | | |
43 | 46 | | |
44 | 47 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
0 commit comments