Skip to content

Fix SQL vulnerability in /webapp/findQueryHistory endpoint#999

Open
byungnam wants to merge 1 commit into
trinodb:mainfrom
byungnam:fix-sql-injection
Open

Fix SQL vulnerability in /webapp/findQueryHistory endpoint#999
byungnam wants to merge 1 commit into
trinodb:mainfrom
byungnam:fix-sql-injection

Conversation

@byungnam
Copy link
Copy Markdown

Description

Fix SQL injection vulnerability described in #991

Additional context and related issues

Replaced raw string concatenation with parameters. User input is no longer interpolated into the SQL string.

Release notes

( ) This is not user-visible or is docs only, and no release notes are required.
(x) Release notes are required, with the following suggested text:

* Fix SQL injection vulnerability in /webapp/findQueryHistory endpoint

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Mar 30, 2026

Thank you for your pull request and welcome to the Trino community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. Continue to work with us on the review and improvements in this PR, and submit the signed CLA to cla@trino.io. Photos, scans, or digitally-signed PDF files are all suitable. Processing may take a few days. The CLA needs to be on file before we merge your changes. For more information, see https://github.com/trinodb/cla

@byungnam
Copy link
Copy Markdown
Author

I've submited CLA a few hours ago.

@ebyhr
Copy link
Copy Markdown
Member

ebyhr commented Mar 30, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the query history filtering mechanism from dynamically constructed SQL condition strings to individual parameterized filter parameters. The QueryHistoryDao updates replace @Define("condition")-based dynamic SQL with fixed WHERE clauses using nullable bind parameters for userName, externalUrl, queryId, and source. A new pagination variant using OFFSET ... ROWS FETCH NEXT ... ROWS ONLY syntax is introduced. HaQueryHistoryManager is updated to pass individual filter parameters to the dao instead of building condition strings. A SQL injection test case is added to verify that injection attempts in filter fields return empty results.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java (2)

39-40: Prefer cleanup over ordered stateful tests.

@TestMethodOrder makes this deterministic, but with @TestInstance(Lifecycle.PER_CLASS) every new test now inherits rows written by previous ones. Resetting query_history between tests would keep assertions independent and avoid hidden ordering constraints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java`
around lines 39 - 40, The tests use `@TestInstance`(Lifecycle.PER_CLASS) and
`@TestMethodOrder` on BaseTestQueryHistoryManager which causes state (rows in
query_history) to leak between tests; add a cleanup that resets query_history
after each test instead of relying on ordering. Implement an `@AfterEach` method
(e.g., cleanupQueryHistory or resetQueryHistory) inside
BaseTestQueryHistoryManager that truncates or deletes all rows from the
query_history table via the same database client/QueryHistoryManager used by the
tests, and ensure it runs for every test to keep assertions independent
(alternatively remove PER_CLASS or TestMethodOrder if per-test instance behavior
is acceptable).

149-173: Cover the remaining changed query paths in this regression.

This test only inspects getRows(), but the production change also replaced QueryHistoryDao.count(...) in gateway-ha/src/main/java/io/trino/gateway/ha/persistence/dao/QueryHistoryDao.java Lines 136-147 and parameterized userName too. Please add one injected user case and assert the reported total/count so a regression there cannot slip through.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java`
around lines 149 - 173, Add a test that injects SQL into the user field and
asserts the returned total/count from the paged response so the DAO.count
regression is covered: create a QueryHistoryRequest with a malicious user value
(e.g. "' OR '1'='1") and call queryHistoryManager.findQueryHistory(...) and
assert the TableData returned has total/count == 0 (use TableData.getTotal() or
its equivalent), then also assert the normalRequest still reports total/count ==
1; this ensures both QueryHistoryRequest handling in
queryHistoryManager.findQueryHistory and the QueryHistoryDao.count(...)
parameterization are exercised.
gateway-ha/src/main/java/io/trino/gateway/ha/router/HaQueryHistoryManager.java (1)

127-132: Normalize the page size once and reuse for both offset and limit calculations.

QueryHistoryRequest has no validation annotations and does not enforce that size is positive. Line 127 clamps bad sizes inside getStart(), but line 132 still passes the raw query.size() to the DAO as the limit. This creates an inconsistency: when size is non-positive, the offset becomes 0 (normalized), but the limit remains unchanged (not normalized), leading to backend-dependent behavior. Extract a normalized size value and pass it to both getStart() and pageQueryHistory().

Suggested change
-        int start = getStart(query.page(), query.size());
+        int size = Math.max(query.size(), 0);
+        int start = getStart(query.page(), size);
         String userName = Strings.emptyToNull(query.user());
         String externalUrl = Strings.emptyToNull(query.externalUrl());
         String queryId = Strings.emptyToNull(query.queryId());
         String source = Strings.emptyToNull(query.source());
-        List<QueryHistory> histories = dao.pageQueryHistory(userName, externalUrl, queryId, source, query.size(), start, isOracleBackend);
+        List<QueryHistory> histories = dao.pageQueryHistory(userName, externalUrl, queryId, source, size, start, isOracleBackend);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@gateway-ha/src/main/java/io/trino/gateway/ha/router/HaQueryHistoryManager.java`
around lines 127 - 132, Normalize the requested page size once and reuse it for
both offset and limit: compute an int normalizedSize =
getValidSize(query.size()) (or call getStart/getSize helper) and pass
normalizedSize into getStart(normalizedSize) and dao.pageQueryHistory(...,
normalizedSize, start, ...) instead of using query.size() directly; update
references around getStart, QueryHistoryRequest, and dao.pageQueryHistory so the
same validated size value is used for both offset and limit to avoid
backend-dependent behavior when size is non-positive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@gateway-ha/src/main/java/io/trino/gateway/ha/router/HaQueryHistoryManager.java`:
- Around line 127-132: Normalize the requested page size once and reuse it for
both offset and limit: compute an int normalizedSize =
getValidSize(query.size()) (or call getStart/getSize helper) and pass
normalizedSize into getStart(normalizedSize) and dao.pageQueryHistory(...,
normalizedSize, start, ...) instead of using query.size() directly; update
references around getStart, QueryHistoryRequest, and dao.pageQueryHistory so the
same validated size value is used for both offset and limit to avoid
backend-dependent behavior when size is non-positive.

In
`@gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java`:
- Around line 39-40: The tests use `@TestInstance`(Lifecycle.PER_CLASS) and
`@TestMethodOrder` on BaseTestQueryHistoryManager which causes state (rows in
query_history) to leak between tests; add a cleanup that resets query_history
after each test instead of relying on ordering. Implement an `@AfterEach` method
(e.g., cleanupQueryHistory or resetQueryHistory) inside
BaseTestQueryHistoryManager that truncates or deletes all rows from the
query_history table via the same database client/QueryHistoryManager used by the
tests, and ensure it runs for every test to keep assertions independent
(alternatively remove PER_CLASS or TestMethodOrder if per-test instance behavior
is acceptable).
- Around line 149-173: Add a test that injects SQL into the user field and
asserts the returned total/count from the paged response so the DAO.count
regression is covered: create a QueryHistoryRequest with a malicious user value
(e.g. "' OR '1'='1") and call queryHistoryManager.findQueryHistory(...) and
assert the TableData returned has total/count == 0 (use TableData.getTotal() or
its equivalent), then also assert the normalRequest still reports total/count ==
1; this ensures both QueryHistoryRequest handling in
queryHistoryManager.findQueryHistory and the QueryHistoryDao.count(...)
parameterization are exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2f30fa1a-ab7b-4995-9764-46bf0f7cd007

📥 Commits

Reviewing files that changed from the base of the PR and between d2d79d2 and b66e9db.

📒 Files selected for processing (3)
  • gateway-ha/src/main/java/io/trino/gateway/ha/persistence/dao/QueryHistoryDao.java
  • gateway-ha/src/main/java/io/trino/gateway/ha/router/HaQueryHistoryManager.java
  • gateway-ha/src/test/java/io/trino/gateway/ha/router/BaseTestQueryHistoryManager.java

@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Mar 31, 2026

Thank you for your pull request and welcome to the Trino community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. Continue to work with us on the review and improvements in this PR, and submit the signed CLA to cla@trino.io. Photos, scans, or digitally-signed PDF files are all suitable. Processing may take a few days. The CLA needs to be on file before we merge your changes. For more information, see https://github.com/trinodb/cla

@mosabua
Copy link
Copy Markdown
Member

mosabua commented Apr 1, 2026

@martint can you please process the PR for @byungnam so we can get this merged and release soon

@github-actions
Copy link
Copy Markdown

This pull request has gone a while without any activity. Ask for help on #trino-gateway-dev on Trino slack.

@github-actions github-actions Bot added the stale label Apr 22, 2026
@mosabua
Copy link
Copy Markdown
Member

mosabua commented Apr 29, 2026

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label Apr 29, 2026
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented Apr 29, 2026

The cla-bot has been summoned, and re-checked this pull request!

@github-actions github-actions Bot removed the stale label Apr 29, 2026
@mosabua
Copy link
Copy Markdown
Member

mosabua commented May 4, 2026

@ebyhr @trinodb/maintainers-gateway .. good to go?

Copy link
Copy Markdown
Member

@Chaho12 Chaho12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please squash commit into 1

@byungnam byungnam force-pushed the fix-sql-injection branch 2 times, most recently from cbbe436 to 7c273d5 Compare May 13, 2026 02:34
@Chaho12
Copy link
Copy Markdown
Member

Chaho12 commented May 13, 2026

Can you resolve conflicts?

@byungnam byungnam force-pushed the fix-sql-injection branch from 7c273d5 to 4237dcc Compare May 13, 2026 03:12
@byungnam
Copy link
Copy Markdown
Author

Can you resolve conflicts?

done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants