Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions .config/CredScanSuppressions.json

This file was deleted.

2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ python -m pytest -v # 'stress' marker excl

## Security and credentials

- **Committed connection strings that contain `UID`/`PWD` must use `SERVER=localhost` (or `127.0.0.1`) with dummy values.** Real remote or Azure credentials come only from secrets or the `DB_CONNECTION_STRING` env var, and are never committed. Automated credential scanning (see `.config/CredScanSuppressions.json`, `.gdn/`) can block unsafe patterns.
- **Committed connection strings that contain `UID`/`PWD` must use `SERVER=localhost` (or `127.0.0.1`) with dummy values.** Real remote or Azure credentials come only from secrets or the `DB_CONNECTION_STRING` env var, and are never committed. Automated credential scanning (see `.gdn/`) can block unsafe patterns.
- Do **not** put `Driver=` in a connection string — the bundled driver is selected automatically.
- `TrustServerCertificate=yes` is local-development only; never suggest it in remote or production examples.

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/devskim.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
schedule:
- cron: '44 7 * * 5'

permissions:
contents: read

jobs:
lint:
name: DevSkim
Expand All @@ -24,6 +27,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Run DevSkim scanner
uses: microsoft/DevSkim-Action@v1
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/forked-pr-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ on:
types:
- completed

permissions:
contents: read

jobs:
post-comment:
runs-on: ubuntu-latest
Expand All @@ -35,6 +38,8 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Download coverage data
env:
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/lint-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ on:
- main

permissions:
pull-requests: write
contents: read

jobs:
python-lint:
Expand All @@ -29,6 +29,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v5
Expand Down Expand Up @@ -85,6 +87,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Python (for cpplint)
uses: actions/setup-python@v5
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pr-code-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
branches:
- main

permissions:
contents: read

jobs:
coverage-report:
runs-on: ubuntu-latest
Expand All @@ -17,6 +20,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- name: Setup git for diff-cover
run: |
Expand Down
29 changes: 27 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
This file contains fixtures for the tests in the mssql_python package.
Functions:
- pytest_configure: Add any necessary configuration.
- conn_str: Fixture to get the connection string from environment variables.
- conn_str: Fixture to get the connection string from environment variables,
wrapped so its password is not printed in pytest failure output.
- db_connection: Fixture to create and yield a database connection.
- cursor: Fixture to create and yield a cursor from the database connection.
- is_azure_sql_connection: Helper function to detect Azure SQL Database connections.
Expand All @@ -12,9 +13,33 @@
import os
import re
from mssql_python import connect
from mssql_python.connection_string_parser import sanitize_connection_string
import time


class _MaskedConnectionString(str):
"""A str that behaves like the connection string but never reveals its
password when repr()'d.

pytest prints every test argument in the failure header (``conn_str =
'...'``) and uses repr() to do it, so a plain str fixture puts the live
credential into CI logs on any failure in any test that takes conn_str,
not just the ones asserting on connection strings. Masking repr() keeps
the value fully usable while keeping the password out of that output.

This has to be a subclass rather than a call to
sanitize_connection_string() at the point of use: pytest reads repr() off
the object it holds, str.__repr__ cannot be reassigned on the builtin, and
sanitizing the fixture value itself would leave the tests unable to
connect.
"""

__slots__ = ()

def __repr__(self):
return repr(sanitize_connection_string(str(self)))


def is_qemu_emulated():
"""Detect if running under QEMU user-mode emulation (e.g. ARM64 on x86_64 host).

Expand Down Expand Up @@ -53,7 +78,7 @@ def pytest_configure(config):
@pytest.fixture(scope="session")
def conn_str():
conn_str = os.getenv("DB_CONNECTION_STRING")
return conn_str
return _MaskedConnectionString(conn_str) if conn_str else conn_str


@pytest.fixture(scope="module")
Expand Down
23 changes: 13 additions & 10 deletions tests/test_012_connection_string_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from mssql_python.connection_string_parser import (
_ConnectionStringParser,
ConnectionStringParseError,
sanitize_connection_string,
)
from mssql_python.connection_string_builder import _ConnectionStringBuilder
from mssql_python import connect
Expand Down Expand Up @@ -493,12 +494,13 @@ def test_connect_with_real_database(self, conn_str):
conn = connect(conn_str)
assert conn is not None

# Assert on the sanitized string so a failure here cannot print the
# live credential through pytest assertion introspection.
sanitized = sanitize_connection_string(conn.connection_str)

# Verify connection string has required parameters
assert "Driver=" in conn.connection_str or "driver=" in conn.connection_str
assert (
"APP=MSSQL-Python" in conn.connection_str
or "app=mssql-python" in conn.connection_str.lower()
)
assert "Driver=" in sanitized or "driver=" in sanitized
assert "APP=MSSQL-Python" in sanitized or "app=mssql-python" in sanitized.lower()

# Test basic query execution
cursor = conn.cursor()
Expand All @@ -521,12 +523,13 @@ def test_connect_kwargs_override_with_real_database(self, conn_str):
# Verify connection works and autocommit is set
assert conn.autocommit == True

# Assert on the sanitized string so a failure here cannot print the
# live credential through pytest assertion introspection.
sanitized = sanitize_connection_string(conn.connection_str)

# Verify connection string still has all required params
assert "Driver=" in conn.connection_str or "driver=" in conn.connection_str
assert (
"APP=MSSQL-Python" in conn.connection_str
or "app=mssql-python" in conn.connection_str.lower()
)
assert "Driver=" in sanitized or "driver=" in sanitized
assert "APP=MSSQL-Python" in sanitized or "app=mssql-python" in sanitized.lower()

conn.close()

Expand Down
Loading