Skip to content

FIX: Redact parameter values from executemany Decimal-conversion error - #719

Open
Sumit Sarabhai (sumitmsft) wants to merge 3 commits into
mainfrom
sumitmsft-argus-cf-024
Open

FIX: Redact parameter values from executemany Decimal-conversion error#719
Sumit Sarabhai (sumitmsft) wants to merge 3 commits into
mainfrom
sumitmsft-argus-cf-024

Conversation

@sumitmsft

@sumitmsft Sumit Sarabhai (sumitmsft) commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

AB#47300


Summary

The executemany() Decimal/NUMERIC conversion path embedded the entire parameter row into the raised ValueError:

ValueError(f"Failed to convert parameter at row {row}, column {i} to Decimal: {e}")

Here row is the full parameter tuple, so every column value in a failing row (potentially PII such as SSNs, emails, names, or balances) was placed into the exception message. That message propagates to caller error handlers, tracebacks, and APM/log shippers (Sentry, Splunk, App Insights) even when driver DEBUG logging is never enabled, because uncaught exceptions surface by default.

This change makes the error metadata-only: it reports the row index, column index, and the value's type name, and never the value or the row.

Before

Failed to convert parameter at row ('Jane Doe', '123-45-6789', 'jane@x.com', 'N/A'), column 3 to Decimal: [<class 'decimal.ConversionSyntax'>]

After

Failed to convert parameter to Decimal at row 0, column 3 (value type: str)

Hardening: value-bearing exception cause

Redacting only the outer message is not enough. raise ... from e chains the original exception, which surfaces through __cause__ and traceback.format_exc() -- exactly what APM/log shippers capture. For an ordinary bad string the chained cause is the value-free decimal.InvalidOperation (ConversionSyntax), but for a value whose str() raises with content (or any error echoing the input) the sensitive text would still leak via the cause.

The conversion now splits str(val) from the decimal parse and chains only decimal.DecimalException, which is proven value-free (it never echoes the input). str(val) failures and any other unexpected error are re-raised with the chain suppressed (from None), so the metadata-only guarantee holds across __cause__ and formatted tracebacks, not just str(exc).

Changes

  • mssql_python/cursor.py: enumerate rows for an index; raise a value-free, metadata-only ValueError; chain only the value-free decimal.DecimalException and suppress the cause (from None) for str(val) / unexpected failures.
  • tests/test_004_cursor.py: strengthen the unconvertible-value test to assert the sensitive value and raw row are absent from both the message and the fully formatted traceback; add test_setinputsizes_sql_decimal_str_raises_no_leak covering a parameter whose str() raises a secret (asserts __cause__ is None and the secret is absent from the traceback).

…r (AB#47300)

The executemany() Decimal/NUMERIC conversion path embedded the full parameter row into the raised ValueError, so every column value in a failing row (potentially PII such as SSNs, emails, or balances) leaked into caller error handlers, tracebacks, and log/APM stores even without DEBUG logging enabled.

Report metadata only (row index, column index, value type). The original decimal error is preserved via exception chaining.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 13, 2026 17:46
@github-actions github-actions Bot added the pr-size: small Minimal code update label Aug 13, 2026

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 updates the executemany() DECIMAL/NUMERIC conversion error path to avoid leaking full parameter rows (potentially containing PII) into exception messages, while preserving debugging detail via exception chaining.

Changes:

  • Redacts parameter values/rows from the ValueError raised during DECIMAL conversion in Cursor.executemany(), replacing them with row/column index + value type metadata.
  • Strengthens the integration test to assert that sensitive values and raw parameter-row representations are not present in the raised error message.

Reviewed changes

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

File Description
mssql_python/cursor.py Changes DECIMAL/NUMERIC conversion failure message to include only row/column indices and value type (no value/row), using exception chaining.
tests/test_004_cursor.py Adds assertions that the error message includes metadata and does not include the sensitive value / raw row.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/test_004_cursor.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…or (AB#47300)

The prior redaction cleaned the outer ValueError message but chained the raw exception via 'from e'. For a parameter whose str() raises with content (or any exception echoing the input), that cause surfaces through __cause__ and traceback.format_exc(), which APM/log shippers capture -- defeating the metadata-only guarantee the threat model requires.

Now only decimal.DecimalException (proven value-free, e.g. ConversionSyntax) is chained for debuggability; str(val) failures and other unexpected errors are re-raised with the chain suppressed (from None). Adds a test asserting the formatted traceback leaks neither the value nor a str()-raised secret.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added pr-size: medium Moderate update size and removed pr-size: small Minimal code update labels Aug 13, 2026
@github-actions

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

81%


🎯 Overall Coverage

82%


📈 Total Lines Covered: 7373 out of 8970
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/cursor.py (81.8%): Missing lines 2696-2697

Summary

  • Total: 11 lines
  • Missing: 2 lines
  • Coverage: 81%

mssql_python/cursor.py

Lines 2692-2701

  2692                         try:
  2693                             processed_row[i] = format(decimal.Decimal(val_text), "f")
  2694                         except decimal.DecimalException as e:
  2695                             raise ValueError(err_msg) from e
! 2696                         except Exception:  # pylint: disable=broad-exception-caught
! 2697                             raise ValueError(err_msg) from None
  2698             processed_parameters.append(processed_row)
  2699 
  2700         # Now transpose the processed parameters
  2701         columnwise_params, row_count = self._transpose_rowwise_to_columnwise(processed_parameters)


📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 59.9%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 76.6%
mssql_python.__init__.py: 77.6%
mssql_python.row.py: 77.6%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 81.4%
mssql_python.pybind.connection.connection.cpp: 84.3%
mssql_python.logging.py: 85.5%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

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

Labels

pr-size: medium Moderate update size

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants