Skip to content

Parameterize offline-storage deletes and harden kill-switch response handling#1467

Open
bmehta001 wants to merge 7 commits into
microsoft:mainfrom
bmehta001:bhamehta/offline-storage-parameterized-deletes
Open

Parameterize offline-storage deletes and harden kill-switch response handling#1467
bmehta001 wants to merge 7 commits into
microsoft:mainfrom
bmehta001:bhamehta/offline-storage-parameterized-deletes

Conversation

@bmehta001

@bmehta001 bmehta001 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Builds OfflineStorage_SQLite::DeleteRecords as a parameterized prepared statement and tightens kill-switch response handling.

Changes

  • OfflineStorage_SQLite::DeleteRecords now builds a parameterized statement: column names are taken from a fixed whitelist and values are bound via sqlite3_bind_* on a single prepared statement instead of being concatenated into the SQL text. This is more robust and avoids quoting/escaping edge cases for values such as tenant tokens.
  • DeleteRecords no longer issues a DELETE with an empty predicate.
  • KillSwitchManager::handleResponse validates kill-token values against the expected tenant-token character set before storing them.
  • KillSwitchManager::handleResponse now parses the Retry-After and kill-duration response headers through a non-throwing helper, so a malformed value (for example the RFC 7231 HTTP-date form of Retry-After) is ignored rather than propagating out of the SDK worker thread.
  • Adds OfflineStorageTests_SQLite regression tests for deletion by tenant_token (including values containing unusual characters), verifying that only matching records are removed.
  • Adds KillSwitchManagerTests covering valid input plus malformed Retry-After / kill-duration values and a rejected malformed kill-token.

Validation

Full OfflineStorageTests_SQLite suite (27 tests) plus new KillSwitchManagerTests (7 tests) pass on Windows x64 Debug - 34 total.

Build OfflineStorage_SQLite::DeleteRecords as a parameterized prepared statement: column names come from a fixed whitelist and values are bound via sqlite3_bind_* on a single statement instead of being concatenated into the SQL text. This is more robust and avoids quoting/escaping edge cases for values such as tenant tokens. DeleteRecords also no longer issues a DELETE with an empty predicate.

Validate kill-token values against the expected tenant-token character set before storing them, and add OfflineStorageTests_SQLite regression tests covering deletion by tenant_token (including values with unusual characters).

Validated: full OfflineStorageTests_SQLite suite (27 tests) passes on Windows x64 Debug, including the new tests.

Files changed:

- lib/offline/OfflineStorage_SQLite.cpp

- lib/offline/KillSwitchManager.hpp

- tests/unittests/OfflineStorageTests_SQLite.cpp

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bmehta001 bmehta001 requested a review from a team as a code owner June 8, 2026 07:01
The Retry-After and kill-duration response headers were parsed with
std::stoi without guarding against non-numeric or out-of-range input.
Because the SDK worker thread executes queued tasks without an exception
guard, an unparseable header value (for example the standards-compliant
HTTP-date form of Retry-After permitted by RFC 7231) could let the
exception escape the worker thread and terminate the process. Route both
parses through a non-throwing helper that ignores values it cannot parse.

Add KillSwitchManager unit tests covering valid input plus malformed
Retry-After / kill-duration values and a rejected malformed kill-token.

Files changed:
- lib/offline/KillSwitchManager.hpp
- tests/unittests/KillSwitchManagerTests.cpp (new)
- tests/unittests/CMakeLists.txt
- tests/unittests/UnitTests.vcxproj
- tests/unittests/UnitTests.vcxproj.filters

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens offline-storage deletion and kill-switch handling by switching OfflineStorage_SQLite::DeleteRecords to a parameterized SQLite prepared statement, avoiding concatenation of untrusted values into SQL and preventing accidental table-wide deletes.

Changes:

  • Reworks OfflineStorage_SQLite::DeleteRecords to use a whitelisted set of column names and sqlite3_bind_* value binding, and to bail out when no valid predicate is provided.
  • Makes KillSwitchManager::handleResponse resilient to malformed Retry-After / kill-duration values and rejects malformed kill-token tenant IDs.
  • Adds unit tests covering deletion by tenant_token (including SQL-injection-shaped input) and new KillSwitchManager header parsing behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unittests/UnitTests.vcxproj.filters Adds the new KillSwitchManager unit test source to VS project filters.
tests/unittests/UnitTests.vcxproj Adds the new KillSwitchManager unit test source to the unit test project build.
tests/unittests/OfflineStorageTests_SQLite.cpp Adds regression tests validating tenant-token deletes and SQL-injection-shaped input handling.
tests/unittests/KillSwitchManagerTests.cpp Introduces coverage for Retry-After, kill-token, and kill-duration parsing/validation.
tests/unittests/CMakeLists.txt Adds KillSwitchManager tests to the CMake unit test target sources.
lib/offline/OfflineStorage_SQLite.cpp Implements parameterized DELETE statement creation/binding and avoids empty-predicate deletes.
lib/offline/KillSwitchManager.hpp Adds safe duration parsing helper and tenant-token character validation before storing kill tokens.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/offline/OfflineStorage_SQLite.cpp Outdated
Comment thread lib/offline/OfflineStorage_SQLite.cpp
Comment thread lib/offline/OfflineStorage_SQLite.cpp Outdated
Comment thread lib/offline/KillSwitchManager.hpp
@bmehta001 bmehta001 changed the title Use parameterized queries for offline storage deletes Parameterize offline-storage deletes and harden kill-switch response handling Jun 8, 2026
bmehta001 and others added 3 commits June 8, 2026 09:48
Describe what the token validation and response-header parsing do without
spelling out threat-model detail. No behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Log the rejected column name when DeleteRecords ignores an
  unrecognized filter column, to aid diagnosing filter-key typos.
- Check sqlite3_bind_* return codes and abort the delete (logging
  sqlite3_errmsg) instead of executing with unbound parameters.
- Treat a non-numeric value for an integer filter column as an invalid
  filter and abort, rather than coercing to 0 and deleting rows that
  happen to match 0.
- Check the result of SqliteStatement::execute() and log on failure so
  a failed DELETE is not silent.
- KillSwitchManager::tryParseSeconds sets outSeconds=0 on parse failure
  so callers cannot read a stale value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Self-review follow-up: MemoryStorage's matcher treats an unknown filter
column as "no match" and deletes nothing, but the SQLite path dropped the
unknown column and ran the remaining predicate. That could delete more
rows than intended and diverge from MemoryStorage even though
OfflineStorageHandler sends the same filter to both storages. Abort the
delete (remove nothing) when any filter column is outside the whitelist,
and add a regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread lib/offline/KillSwitchManager.hpp
tryParseSeconds used std::stoll without checking how many characters were
consumed, so a numeric prefix followed by garbage (e.g. "120; foo") was
accepted as 120 - contradicting the "ignore malformed values" contract and
potentially activating Retry-After / kill-duration from a malformed header.
Validate full consumption (allowing only trailing whitespace) and add
regression tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread lib/offline/KillSwitchManager.hpp Outdated
The header uses std::vector (handleResponse) and std::list (getTokensList)
but relied on transitive includes. Include them directly so the header is
self-contained and not sensitive to include order.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment on lines +201 to +206
// A tenant token is the API ingestion key tenant id: alphanumeric with
// '-', '_' or '.' separators. Reject anything outside that set.
static bool isValidTenantToken(const std::string& token)
{
if (token.empty() || token.size() > 256)
{
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants