Skip to content

FIX: Remove credential exposure from CI/CD pipelines - #715

Merged
gargsaumya merged 22 commits into
mainfrom
saumya/fix-ci-credential-exposure
Sep 21, 2026
Merged

gargsaumya merged 22 commits into
mainfrom
saumya/fix-ci-credential-exposure

Conversation

@gargsaumya

@gargsaumya gargsaumya commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

AB#46465

GitHub Issue: #<ISSUE_NUMBER>


Summary

This pull request improves the handling of sensitive credentials and environment variables for SQL Server setup and testing across development scripts and CI pipelines. The changes enhance security by avoiding plaintext password exposure, standardize environment variable usage, and make scripts more robust and portable.

Temporary credential files now use owner-only permissions, unique names where applicable, termination-safe cleanup, and fail-closed deletion. SQL Server setup configuration paths are quoted so temporary directories containing spaces remain supported.

Credential Management and Security Improvements:

  • In .devcontainer/post-create.sh, SQL Server passwords are now written to a secure temporary environment file (umask 077), and the credentials are kept in a user-only file (~/.mssql_python_env). The script also removes plaintext credentials from /etc/environment and shell RC files, and ensures credentials are sourced securely in new shell sessions. [1] [2]
  • Docker containers for SQL Server in CI pipelines now receive passwords via environment variables (MSSQL_SA_PASSWORD, SQLCMDPASSWORD) instead of command-line arguments, reducing the risk of leaking secrets in process lists or logs. [1] [2] [3] [4]

Pipeline and Script Robustness:

  • Shell scripts in CI pipelines (build-linux-single-stage.yml, stress-test-pipeline.yml) have been updated to use stricter error handling (set -euo pipefail) for improved reliability and easier debugging. [1] [2] [3] [4] [5] [6] [7] [8]

Cross-Platform and Pipeline Consistency:

  • Windows and Linux pipelines now consistently use environment variables for passing sensitive information to SQL Server and sqlcmd, including for database and user creation. This includes changes to how PowerShell scripts interpolate passwords, and how SQL Server is installed/configured on Windows using a secure configuration file. [1] [2] [3] [4] [5] [6] [7] [8] [9]

Backward Compatibility and Cleanup:

  • The devcontainer script now removes legacy plaintext password files and entries, ensuring no old credentials are left behind after upgrades.

These changes collectively make the development and CI environments more secure and maintainable by standardizing credential handling and improving script reliability.

Copilot AI lite review requested due to automatic review settings August 12, 2026 05:52

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 CI/CD and devcontainer workflows to reduce credential exposure (avoiding bash xtrace, avoiding secrets in process arguments, and limiting secret persistence to restricted files/env).

Changes:

  • Removes -x tracing from multiple bash blocks (set -euo pipefail) and switches several Docker env injections to -e VAR (inherit by name).
  • Stops passing SQL passwords on sqlcmd command lines by using SQLCMDPASSWORD / SQLCMDPASSWORD-style env flows and stdin for sensitive SQL statements.
  • Updates devcontainer SQL startup to use a restricted --env-file and stores DB_CONNECTION_STRING in a user-only env file instead of /etc/environment.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
OneBranchPipelines/stress-test-pipeline.yml Reduces secret exposure during stress-test DB/container setup (env injection + SQLCMDPASSWORD).
OneBranchPipelines/stages/wheel-installation-test-windows-stage.yml Avoids embedding DB passwords directly in the sqlcmd -Q command line.
OneBranchPipelines/stages/wheel-installation-test-stage.yml Removes bash tracing and shifts Docker/sqlcmd password passing to inherited env vars.
OneBranchPipelines/stages/wheel-installation-test-macos-stage.yml Uses inherited MSSQL_SA_PASSWORD / SQLCMDPASSWORD for container startup and readiness checks.
OneBranchPipelines/stages/build-windows-single-stage.yml Avoids embedding DB passwords directly in the sqlcmd -Q command line.
OneBranchPipelines/stages/build-macos-single-stage.yml Uses SQLCMDPASSWORD instead of -P for container readiness checks.
OneBranchPipelines/stages/build-linux-single-stage.yml Removes bash tracing and shifts Docker/sqlcmd password passing to inherited env vars.
eng/pipelines/pypi-package-smoketest.yml Stops echoing the connection string and passes it into containers via inherited env.
eng/pipelines/pr-validation-pipeline.yml Moves SQL installer password to ACL-restricted config files and switches sqlcmd auth to SQLCMDPASSWORD.
.devcontainer/post-create.sh Uses a restricted Docker env file and stores the connection string in a user-only env file sourced by shells.

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

Comment thread .devcontainer/post-create.sh Outdated
Comment thread OneBranchPipelines/stress-test-pipeline.yml Outdated
Comment thread OneBranchPipelines/stress-test-pipeline.yml Outdated
Comment thread OneBranchPipelines/stages/wheel-installation-test-windows-stage.yml Outdated
Comment thread OneBranchPipelines/stages/build-windows-single-stage.yml Outdated
Comment thread eng/pipelines/pr-validation-pipeline.yml Outdated
Comment thread eng/pipelines/pr-validation-pipeline.yml Outdated
Comment thread eng/pipelines/pr-validation-pipeline.yml Outdated
Eliminate secret leakage via bash xtrace, process arguments, and world-readable files across ADO/OneBranch pipelines and the devcontainer:

- Replace 'set -eux' with 'set -euo pipefail' in all secret-handling blocks and nested shells

- Map secrets through step env: and reference shell vars instead of ADO \ macros

- Use SQLCMDPASSWORD and stdin for sqlcmd; inherit Docker env vars by name (-e VAR)

- Replace SQL Server installer /SAPWD= with an ACL-restricted /ConfigurationFile= deleted in finally

- devcontainer: use --env-file (0600) for docker run and a user-only env file instead of world-readable /etc/environment
@gargsaumya
gargsaumya force-pushed the saumya/fix-ci-credential-exposure branch from e0efe43 to ebe74ee Compare August 12, 2026 05:58

@github-advanced-security github-advanced-security 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.

devskim found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@github-actions github-actions Bot added the pr-size: medium Moderate update size label Aug 12, 2026
Avoid mutating the global umask; create per-file with owner-only perms via a scoped subshell and pre-write chmod.
@gargsaumya

Copy link
Copy Markdown
Contributor Author

Re: DevSkim ("20+ potential problems") — these are informational (non-blocking per repo convention) and are the expected local-test-scaffolding patterns: TrustServerCertificate=yes, sqlcmd invocations, and the masked Pwd=*** template strings (literal, no secret expansion). None expose credentials. Existing # DevSkim: ignore suppressions already cover these categories; will add targeted suppressions for any genuinely new line rather than change behavior.

The PowerShell here-string content sat at column 0, which dedented out of the YAML '|' block and made the ADO parser reject the pipeline. Replace it with an indented PowerShell array written via Set-Content, keeping clean INI output.
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

100%


🎯 Overall Coverage

83%


📈 Total Lines Covered: 8639 out of 10295
📁 Project: mssql-python


Diff Coverage

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

No lines with coverage information in this diff.


📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.performance_counter.hpp: 0.7%
mssql_python.pybind.logger_bridge.cpp: 57.9%
mssql_python.pybind.ddbc_bindings.h: 64.1%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 78.3%
mssql_python.pybind.connection.connection_pool.cpp: 82.3%
mssql_python.pybind.connection.connection.cpp: 82.5%
mssql_python.row.py: 83.4%
mssql_python.logging.py: 86.2%
mssql_python.pooling.py: 90.1%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

@gargsaumya

Copy link
Copy Markdown
Contributor Author

Copilot resolve the merge conflicts in this pull request

…ial-exposure

# Conflicts:
#	eng/pipelines/pr-validation-pipeline.yml

Co-authored-by: gargsaumya <192222169+gargsaumya@users.noreply.github.com>

Copilot AI commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in 7d54c59 (true merge commit of origin/main into the branch).

Only eng/pipelines/pr-validation-pipeline.yml conflicted, in two places where main had restructured SQL Server startup:

  • macOS job: main merged the Colima/SQL startup into an overlapped setup_sql() function. Kept that structure and re-applied this PR's hardening — docker run -e MSSQL_SA_PASSWORD (inherit by name) and docker exec -e SQLCMDPASSWORD ... sqlcmd with no -P on the command line — plus the MSSQL_SA_PASSWORD/SQLCMDPASSWORD step env: mappings that structure needs.
  • CodeCoverageReport job: kept main's sql_ready flag and post-loop retry probe, with the readiness checks using -e SQLCMDPASSWORD instead of -P "$(DB_PASSWORD)".

main also added six more "one last probe" blocks in auto-merged regions that reintroduced -P "$(DB_PASSWORD)" on the sqlcmd command line; those were converted to -e SQLCMDPASSWORD as well, so no password is passed as a process argument anywhere in the file. YAML parses clean, no source-code files differ from main, and CodeQL reports 0 alerts.

Escape single quotes before embedding DB_PASSWORD in CREATE LOGIN, disable sqlcmd variable expansion, and make Windows setup fail immediately when sqlcmd rejects the login statement.
Windows PowerShell 5.1 does not reliably pipe the generated SQL text to native sqlcmd. Start sqlcmd directly and write to its redirected standard input so the password stays off the command line and the login statement is encoded correctly.
Azure's Windows PowerShell 5.1 environment writes a UTF-8 BOM through Process.StandardInput, which SQL Server rejects before CREATE LOGIN. Encode the SQL explicitly as BOM-free UTF-8 bytes and tell sqlcmd to read code page 65001.
Process.StandardInput constructs a StreamWriter that can emit its own BOM before raw writes on hosted Windows agents. Write the login statement to a restricted BOM-free UTF-8 file, pass only its path to sqlcmd, and remove it in a finally block.
Copilot AI review requested due to automatic review settings September 9, 2026 06:53

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.

🟡 Changes recommended

The SQL Server readiness loops in eng/pipelines/pr-validation-pipeline.yml rely on try/catch around sqlcmd, which won’t catch native-process failures in Windows PowerShell and can lead to infinite waits.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

eng/pipelines/pr-validation-pipeline.yml:270

  • Same issue as the SQL2022 readiness loop: try/catch won’t catch a failed sqlcmd native-process exit code in Windows PowerShell, so $attempt won’t increment and the loop can run forever. Switch to -b plus an explicit $LASTEXITCODE check for retries.
      Write-Host "Waiting for SQL Server 2025 to start..."
      while (-not $connected -and $attempt -lt $maxAttempts) {
        try {
          sqlcmd -S "localhost" -U "sa" -Q "SELECT 1" -C
          $connected = $true
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread eng/pipelines/pr-validation-pipeline.yml
…ial-exposure

# Conflicts:
#	eng/pipelines/pr-validation-pipeline.yml

Co-authored-by: gargsaumya <192222169+gargsaumya@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 18, 2026 09:08

Copilot AI commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in merge commit 1e38897. Merged origin/main and fixed the conflicts in eng/pipelines/pr-validation-pipeline.yml (SQL2022/SQL2025 setup blocks and the profiler benchmark docker exec block), keeping the secure credential handling while pulling in the new DMV-read permission grant and profiler hardening from main.

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

PR Performance Report

No consistent slowdowns detected across all 2 environments.

Coverage: 2 of 2 environments completed. Advisory result; does not block merging.

Environment Status
Unix / SQL Server 2022 Completed
Unix / SQL Server 2025 Completed
Affected phases and call counts

Phase times are inclusive diagnostics and must not be added together. They identify where measured time changed, not why it changed.

No affected phases or call-count changes were recorded.

All database tasks and timings

Unix / SQL Server 2022

Database task Before After Paired change Result
Connection opening 10.376 ms 10.318 ms -0.6% no signal
SELECT queries 1.328 ms 1.178 ms -11.3% no signal
Row insertion 34.534 ms 34.381 ms -0.9% no signal
Executemany inserts 159.944 ms 155.172 ms -3.3% no signal
Fetch-all queries 172.287 ms 171.417 ms -0.6% no signal
Row-by-row fetching 60.990 ms 60.253 ms -1.5% no signal
Batched row fetching 164.731 ms 178.717 ms +9.3% no signal
Transaction commit and rollback 113.311 ms 113.304 ms +0.3% no signal
Arrow row fetching 93.709 ms 95.094 ms +1.0% no signal
100,000-row insertion 445.312 ms 442.032 ms -1.6% no signal
Row fetching in batches of 100 223.491 ms 220.821 ms -1.2% no signal
Row fetching in batches of 10,000 187.869 ms 188.162 ms +0.2% no signal
Repeated positional queries 42.768 ms 41.782 ms -0.2% no signal
Repeated named-parameter queries 45.029 ms 44.499 ms -1.6% no signal
Legacy 100,000-row insertion 353.705 ms 355.786 ms +0.6% no signal
Insertion with explicit input sizes 2386.002 ms 2318.078 ms -2.8% no signal
Joined aggregation queries 176.797 ms 176.126 ms -0.4% no signal
Large joined-result fetching 207.966 ms 205.310 ms -1.3% no signal
1.2-million-row fetching 5048.012 ms 5008.644 ms -0.6% no signal
Common table expression queries 5.489 ms 5.362 ms -2.1% no signal

Unix / SQL Server 2025

Database task Before After Paired change Result
Connection opening 98.025 ms 96.270 ms -2.6% no signal
SELECT queries 1.195 ms 1.201 ms -0.8% no signal
Row insertion 32.535 ms 31.731 ms -2.2% no signal
Executemany inserts 135.539 ms 142.626 ms +5.6% no signal
Fetch-all queries 167.405 ms 165.009 ms -2.0% no signal
Row-by-row fetching 51.924 ms 52.254 ms +1.1% no signal
Batched row fetching 152.094 ms 154.957 ms +2.2% no signal
Transaction commit and rollback 103.025 ms 103.474 ms +0.4% no signal
Arrow row fetching 88.962 ms 90.676 ms +2.2% no signal
100,000-row insertion 400.924 ms 395.299 ms -2.7% no signal
Row fetching in batches of 100 197.113 ms 198.808 ms -1.2% no signal
Row fetching in batches of 10,000 185.121 ms 199.673 ms +7.8% no signal
Repeated positional queries 38.450 ms 39.184 ms +1.3% no signal
Repeated named-parameter queries 41.197 ms 40.967 ms -0.6% no signal
Legacy 100,000-row insertion 303.364 ms 323.288 ms +0.9% no signal
Insertion with explicit input sizes 2222.600 ms 2241.464 ms +0.8% no signal
Joined aggregation queries 162.543 ms 165.157 ms +1.9% no signal
Large joined-result fetching 209.931 ms 212.293 ms +3.0% no signal
1.2-million-row fetching 5325.089 ms 5377.687 ms +1.2% no signal
Common table expression queries 5.339 ms 5.314 ms -2.1% no signal
Build, commits and measurement details

ADO build 176906

PR head: bfc26b2504f7cf0603dc53679ba4c60e4969b65b
Base: c963ee1ebf11a96b2dc5275b465d2a36f3f55d77
Measured merge: b4f2341afcbaa3aa4bcc27df843a8674e3ff2d2c

  • Unix / SQL Server 2022: Python 3.12.3, x86_64, SQL 16.0.4295.3; 5 paired comparisons and 1 warmup.
  • Unix / SQL Server 2025: Python 3.12.3, x86_64, SQL 17.0.5005.3; 5 paired comparisons and 1 warmup.

A consistent change requires more than 20% median paired movement, at least 1 ms between the median runtimes, and at least 80% of pairs exceeding the relative threshold in the same direction. A slowdown without enough pair agreement is reported as inconsistent.

The displayed change is the median of paired before-and-after ratios. It is not recalculated from the two displayed median runtimes.

Both revisions use profiling-enabled builds on the same agent and database, with alternating order and discarded warmups. Results are diagnostic and do not represent production-wheel latency.

Raw samples and logs are attached to the ADO run as profiler-* artifacts.

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.

🔵 Needs a closer look

Four moderate issues remain involving secret-file cleanup, file permissions, and configuration-path quoting.

Review details

Suppressed comments (4)

.devcontainer/post-create.sh:58

  • If the post-create script is interrupted while docker run is starting, this secret-bearing temp file is never removed because cleanup runs only after docker run returns. Register an EXIT/termination cleanup trap immediately after mktemp so an interrupted setup does not leave MSSQL_SA_PASSWORD in /tmp.
SQL_ENV_FILE="$(mktemp)"
chmod 600 "$SQL_ENV_FILE"
printf 'ACCEPT_EULA=Y\nMSSQL_SA_PASSWORD=%s\n' "$SA_PASSWORD" > "$SQL_ENV_FILE"

.devcontainer/post-create.sh:95

  • The secret is written before the final chmod 600. On a rerun where .mssql_python_env already has permissive permissions, umask does not change that existing mode, so the new password is briefly written while other users may be able to read it. Create/restrict the file before the printf (inside the scoped-umask block).
( umask 077; printf "export DB_CONNECTION_STRING='%s'\n" "$DB_CONNECTION_STRING" > "$MSSQL_ENV_FILE" )
chmod 600 "$MSSQL_ENV_FILE"

eng/pipelines/pr-validation-pipeline.yml:261

  • This SQL Server 2025 setup invocation has the same path-parsing failure: an unquoted $configPath is split when the temp directory contains spaces, so setup cannot find the configuration file. Pass the argument with escaped quotes around the path.
          Start-Process -FilePath "$env:TEMP\SQL2025Setup\setup.exe" -ArgumentList "/ConfigurationFile=$configPath" -Wait

eng/pipelines/pr-validation-pipeline.yml:155

  • The /ConfigurationFile= argument is passed without quoting the path. Start-Process builds a command line, so if TEMP contains spaces (for example, on a self-hosted agent), the installer receives a truncated path and cannot load the INI. Include escaped quotes around $configPath in the argument; apply the same fix to the SQL Server 2025 invocation below.
          Start-Process -FilePath "$env:TEMP\SQLSetup\setup.exe" -ArgumentList "/ConfigurationFile=$configPath" -Wait
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 18, 2026 09:47

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.

🔵 Needs a closer look

Ensure legacy credential cleanup runs before failure-prone setup or during exit cleanup.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

.devcontainer/post-create.sh:105

  • These migration cleanups run only after the failure-prone dependency installation, native build, password generation, and Docker setup. Because the script uses set -e, an early failure leaves the old /etc/environment, shell-RC, or /tmp/.sqlserver_sa_password credential in place, so an upgrade does not guarantee plaintext removal. Run this legacy cleanup before those operations or register it in the exit cleanup path.
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@gargsaumya

Copy link
Copy Markdown
Contributor Author

Copilot resolve the merge conflicts in this pull request

…ial-exposure

# Conflicts:
#	eng/pipelines/pr-validation-pipeline.yml

Co-authored-by: gargsaumya <192222169+gargsaumya@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 18, 2026 15:24

Copilot AI commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Copilot resolve the merge conflicts in this pull request

Resolved in merge commit 4d73928. Merged origin/main and fixed the conflict in eng/pipelines/pr-validation-pipeline.yml (macOS profiling block), keeping this branch's secure credential handling while adopting main's removal of hosted-macOS profiling and the other unrelated updates (Row _mapping, ODBC wheel libc split, scheduled mssql-odbc pipeline).

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.

🔵 Needs a closer look

The two moderate findings in eng/pipelines/pr-validation-pipeline.yml must be addressed before approval.

Review details

Suppressed comments (2)

eng/pipelines/pr-validation-pipeline.yml:155

  • Set-Content reports write failures as non-terminating errors by default, and Start-Process -Wait does not throw for a non-zero installer exit. If the credential-bearing INI cannot be written or setup fails, this step can still print installation completed and let later steps run against an unconfigured instance. Make the write terminating and check the installer exit code before cleanup.
          Set-Content -Path $configPath -Value $configLines -Encoding ASCII
          Start-Process -FilePath "$env:TEMP\SQLSetup\setup.exe" -ArgumentList "/ConfigurationFile=`"$configPath`"" -Wait

eng/pipelines/pr-validation-pipeline.yml:261

  • Set-Content reports write failures as non-terminating errors by default, and Start-Process -Wait does not throw for a non-zero installer exit. If the credential-bearing INI cannot be written or setup fails, this step can still print installation completed and let later steps run against an unconfigured instance. Make the write terminating and check the installer exit code before cleanup.
          Set-Content -Path $configPath -Value $configLines -Encoding ASCII
          Start-Process -FilePath "$env:TEMP\SQL2025Setup\setup.exe" -ArgumentList "/ConfigurationFile=`"$configPath`"" -Wait
  • Files reviewed: 6/6 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 21, 2026 08:48

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.

Copilot review overview

🔵 Needs a closer look

Two moderate credential-cleanup issues remain unresolved in .devcontainer/post-create.sh.

Review effort: Lite
Findings: None

@gargsaumya
gargsaumya merged commit f3e34e6 into main Sep 21, 2026
39 checks passed
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.

6 participants