From aeab9ddd882b75d3a320f8e23c3bec4d49eae3a5 Mon Sep 17 00:00:00 2001 From: JPPhoto Date: Fri, 13 Feb 2026 15:57:08 -0600 Subject: [PATCH 1/3] Added SQL injection tests --- .../services/test_sql_injection_protection.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/app/services/test_sql_injection_protection.py diff --git a/tests/app/services/test_sql_injection_protection.py b/tests/app/services/test_sql_injection_protection.py new file mode 100644 index 00000000000..13bbb2bfee9 --- /dev/null +++ b/tests/app/services/test_sql_injection_protection.py @@ -0,0 +1,62 @@ +import pytest + +from invokeai.app.services.board_records.board_records_common import ( + BoardRecordNotFoundException, + BoardRecordOrderBy, +) +from invokeai.app.services.board_records.board_records_sqlite import SqliteBoardRecordStorage +from invokeai.app.services.config.config_default import InvokeAIAppConfig +from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection +from invokeai.backend.util.logging import InvokeAILogger +from tests.fixtures.sqlite_database import create_mock_sqlite_database + + +def _create_board_storage() -> SqliteBoardRecordStorage: + config = InvokeAIAppConfig(use_memory_db=True) + db = create_mock_sqlite_database(config=config, logger=InvokeAILogger.get_logger()) + return SqliteBoardRecordStorage(db=db) + + +def test_sql_injection_payload_in_board_name_is_stored_as_plain_text() -> None: + storage = _create_board_storage() + + payload = "name'); DROP TABLE boards; --" + injected_board = storage.save(payload) + + fetched = storage.get(injected_board.board_id) + assert fetched.board_name == payload + + another_board = storage.save("safe board") + assert storage.get(another_board.board_id).board_name == "safe board" + + +def test_sql_injection_payload_in_board_id_does_not_bypass_where_clause() -> None: + storage = _create_board_storage() + + storage.save("first board") + storage.save("second board") + + payload = "does-not-exist' OR '1'='1" + + with pytest.raises(BoardRecordNotFoundException): + storage.get(payload) + + +def test_sql_injection_payload_in_delete_does_not_delete_other_rows() -> None: + storage = _create_board_storage() + + first = storage.save("first board") + second = storage.save("second board") + + payload = f"{first.board_id}' OR '1'='1" + storage.delete(payload) + + remaining = storage.get_many( + order_by=BoardRecordOrderBy.CreatedAt, + direction=SQLiteDirection.Ascending, + limit=10, + offset=0, + include_archived=True, + ) + + assert {board.board_id for board in remaining.items} == {first.board_id, second.board_id} From bbc78bd152724ed4574108960d98e4bf6864eff5 Mon Sep 17 00:00:00 2001 From: JPPhoto Date: Sat, 28 Feb 2026 10:11:48 -0600 Subject: [PATCH 2/3] Updated tests after multi-user merge --- .../app/services/test_sql_injection_protection.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/app/services/test_sql_injection_protection.py b/tests/app/services/test_sql_injection_protection.py index 13bbb2bfee9..63cf9b17d63 100644 --- a/tests/app/services/test_sql_injection_protection.py +++ b/tests/app/services/test_sql_injection_protection.py @@ -21,20 +21,20 @@ def test_sql_injection_payload_in_board_name_is_stored_as_plain_text() -> None: storage = _create_board_storage() payload = "name'); DROP TABLE boards; --" - injected_board = storage.save(payload) + injected_board = storage.save(payload, "0") fetched = storage.get(injected_board.board_id) assert fetched.board_name == payload - another_board = storage.save("safe board") + another_board = storage.save("safe board", "0") assert storage.get(another_board.board_id).board_name == "safe board" def test_sql_injection_payload_in_board_id_does_not_bypass_where_clause() -> None: storage = _create_board_storage() - storage.save("first board") - storage.save("second board") + storage.save("first board", "0") + storage.save("second board", "0") payload = "does-not-exist' OR '1'='1" @@ -45,8 +45,8 @@ def test_sql_injection_payload_in_board_id_does_not_bypass_where_clause() -> Non def test_sql_injection_payload_in_delete_does_not_delete_other_rows() -> None: storage = _create_board_storage() - first = storage.save("first board") - second = storage.save("second board") + first = storage.save("first board", "0") + second = storage.save("second board", "0") payload = f"{first.board_id}' OR '1'='1" storage.delete(payload) @@ -57,6 +57,8 @@ def test_sql_injection_payload_in_delete_does_not_delete_other_rows() -> None: limit=10, offset=0, include_archived=True, + user_id="0", + is_admin=True ) assert {board.board_id for board in remaining.items} == {first.board_id, second.board_id} From 81801f1c35af6ae1b39d1ec74d9d542c0eba6ed8 Mon Sep 17 00:00:00 2001 From: JPPhoto Date: Mon, 2 Mar 2026 10:28:53 -0600 Subject: [PATCH 3/3] ruff:format --- tests/app/services/test_sql_injection_protection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/app/services/test_sql_injection_protection.py b/tests/app/services/test_sql_injection_protection.py index 63cf9b17d63..4efd7b0b8dd 100644 --- a/tests/app/services/test_sql_injection_protection.py +++ b/tests/app/services/test_sql_injection_protection.py @@ -58,7 +58,7 @@ def test_sql_injection_payload_in_delete_does_not_delete_other_rows() -> None: offset=0, include_archived=True, user_id="0", - is_admin=True + is_admin=True, ) assert {board.board_id for board in remaining.items} == {first.board_id, second.board_id}