You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: Add cross-platform credential store for secure connection string management
Store database credentials in OS keychain (Windows Credential Manager,
macOS Keychain, Linux libsecret) via `credentials add/list/remove` subcommand.
Connection strings without explicit credentials auto-inject stored values,
eliminating passwords from CLI args, config files, and process lists.
Centralizes connection string normalization into ConnectionStringHelper.Resolve()
across all 5 entry points. Mitigates STRIDE findings I-1 and I-2.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-**Derived table flattening**: When `FlattenDerivedTables` is enabled, `DerivedTableFlattener` runs as a post-processing step after stripping. It replaces eligible `QueryDerivedTable` nodes with their inner `FROM` tree (single table or JOIN tree), rewrites column references, and merges WHERE clauses. Uses `OuterScopeColumnReferenceCollector` that stops at `QueryDerivedTable` boundaries to prevent corrupting shared AST object references.
100
100
-**`ParametersToIgnore`**: Maps SQL functions (e.g., DATEADD) to parameter indexes that should be excluded from column reference analysis.
101
101
102
+
### Credential subsystem (`Optimize/` directory)
103
+
104
+
Conditionally compiled (`#if !RELEASELIBRARY`) alongside the Optimize subsystem.
105
+
106
+
22.**ICredentialStore** — Interface for platform-specific credential storage. Defines `Store()`, `Retrieve()`, `Remove()`, and `List()`. `StoredCredential` holds username + password. `CredentialStoreFactory.Create()` returns the platform-appropriate implementation (or null with a warning). `CredentialStoreFactory.BuildKey()` normalizes server/database into a `"server\database"` key (lowercase, trimmed).
107
+
108
+
23.**WindowsCredentialStore** — P/Invoke to `advapi32.dll` (Credential Manager). Uses `CRED_TYPE_GENERIC` with target prefix `"sqlinliner:"`. Username in `CREDENTIAL.UserName`, password as UTF-16 bytes in `CredentialBlob`. `List()` uses `CredEnumerate("sqlinliner:*")`.
109
+
110
+
24.**MacCredentialStore** — Wraps macOS `security` CLI (Keychain). Uses a JSON index file (`~/.sqlinliner/credentials.json`) for username lookup and listing (no passwords in index).
111
+
112
+
25.**LinuxCredentialStore** — Wraps `secret-tool` CLI (libsecret). Uses a JSON index file (`~/.sqlinliner/credentials.json`) for username lookup and listing. Constructor checks for `secret-tool` availability with install instructions.
113
+
114
+
26.**ConnectionStringHelper** — Static `Resolve(connectionString, store)` method that replaces the duplicated `SqlConnectionStringBuilder` normalization blocks across all 5 entry points. Sets `ApplicationName`, injects stored credentials if no explicit credentials or Integrated Security, falls back to Integrated Security.
115
+
116
+
27.**CredentialsCommand** — System.CommandLine subcommand (`credentials`) with `add`, `list`, and `remove` sub-commands. `add` prompts for username/password with masked input. `list` displays server/database/username table (never shows passwords). `remove` deletes from the OS credential store. Does not take `configOption`.
117
+
102
118
## Testing Patterns
103
119
104
120
Tests use **NUnit** with **Shouldly** assertions. The standard pattern:
Copy file name to clipboardExpand all lines: README.md
+63Lines changed: 63 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -514,6 +514,69 @@ Two tables are displayed:
514
514
515
515
Plus a summary of views skipped because they have no nested view references.
516
516
517
+
## Credential management
518
+
519
+
The `credentials` subcommand stores database credentials in your OS credential store (Windows Credential Manager, macOS Keychain, or Linux libsecret). This eliminates the need to put passwords in connection strings, CLI arguments, or config files.
520
+
521
+
### Storing credentials
522
+
523
+
```bash
524
+
# Store credentials (prompts for password with masked input)
| I-1 | Connection string exposure via CLI arguments |`--connection-string` visible in process list, shell history, CI/CD logs | 3 | 3 |**9**|**Partially mitigated.**Config file and environment-based alternatives exist, but CLI still accepts plaintext. No built-in redaction. |
151
-
| I-2 | Connection strings in config files |`sqlinliner.json` stores connection strings with embedded passwords; file not gitignored by default | 3 | 3 |**9**|**Partially mitigated.**Users can use Windows Authentication (no password). Config file not gitignored — could be committed accidentally. |
150
+
| I-1 | Connection string exposure via CLI arguments |`--connection-string` visible in process list, shell history, CI/CD logs | 3 | 3 |**9**|**Mitigated.**Built-in credential store stores credentials in OS keychain (Windows Credential Manager, macOS Keychain, Linux libsecret). Passwords never appear in CLI args, process lists, or shell history. |
151
+
| I-2 | Connection strings in config files |`sqlinliner.json` stores connection strings with embedded passwords; file not gitignored by default | 3 | 3 |**9**|**Mitigated.**Config files can specify `Server=...;Database=...` without passwords. Credentials injected at runtime from OS credential store. |
152
152
| I-3 | Session files contain view SQL and metadata | Session directories store full view SQL, execution plans, table names, machine/user names | 2 | 2 | 4 | Files are local to the user's machine. Default OS permissions apply. No automatic cleanup. |
153
153
| I-4 | Exception messages may leak paths or SQL | Error messages expose file paths and database errors to the user | 2 | 1 | 2 | Acceptable for a CLI tool. Errors are displayed to the authenticated user only. |
154
154
| I-5 | Benchmark reports contain environment info | HTML reports include `Environment.MachineName`, `Environment.UserName`, database version, table names | 2 | 2 | 4 | Reports are local files. Users should treat them as internal artifacts. |
155
155
156
156
**Countermeasures:**
157
+
- Built-in credential store (`credentials` subcommand) stores credentials in OS keychain — passwords never appear in CLI args or config files
157
158
- Windows Authentication / Integrated Security eliminates password exposure
159
+
-`ConnectionStringHelper.Resolve()` auto-injects stored credentials at runtime
158
160
-`SqlConnectionStringBuilder` normalizes connection strings (does not add passwords)
159
161
- Session files are local with OS-level access control
160
-
-**Gap:**`sqlinliner.json` is not in `.gitignore` by default — credential leak risk
161
162
-**Gap:** No automatic cleanup of session directories
162
163
163
164
### D — Denial of Service
@@ -196,13 +197,13 @@ sql-inliner is a .NET CLI tool and NuGet library that optimizes SQL Server views
196
197
| ID | Threat | Score | Status |
197
198
|----|--------|-------|--------|
198
199
| T-1 | SQL injection via `--view-name` in `DatabaseConnection.cs`| 8 | Mitigated ([#93](https://github.com/stevehansen/sql-inliner/issues/93)) |
-**T-1 (SQL Injection):** Fixed in [#93](https://github.com/stevehansen/sql-inliner/issues/93). `GetViewDefinition()` and `TryGetRawViewDefinition()` now use Dapper parameterized queries instead of string interpolation.
205
-
-**I-1/I-2 (Credential Exposure):**Connection strings with embedded passwords can leak via process lists, shell history, CI logs, or accidentally committed config files. The recommended approach is Windows Authentication (no password in connection string). Adding `sqlinliner.json` to the `.gitignore` template would reduce the config file risk.
206
+
-**I-1/I-2 (Credential Exposure):**Mitigated by built-in credential store (`credentials` subcommand). Credentials are stored in the OS keychain and auto-injected at runtime. Connection strings in CLI args and config files no longer need embedded passwords.
206
207
207
208
## 4. Security Controls Summary
208
209
@@ -212,7 +213,7 @@ sql-inliner is a .NET CLI tool and NuGet library that optimizes SQL Server views
212
213
| Authorization | Delegates to SQL Server permissions; tool requires VIEW DEFINITION, SELECT, and optionally CREATE/ALTER/DROP VIEW |
| Output Encoding | SQL output generated by `Sql150ScriptGenerator` with proper escaping |
215
-
| Secrets Management |Connection strings via CLI args or config file; no built-in secrets vault integration|
216
+
| Secrets Management |Built-in OS credential store (`credentials` subcommand) for Windows Credential Manager, macOS Keychain, and Linux libsecret; auto-injects stored credentials into connection strings at runtime|
216
217
| Audit Logging | Session logs with timestamps; `validate-errors.log` for batch operations |
217
218
| Error Handling | Exceptions caught at command level; error messages displayed to user |
218
219
| Dependency Security | CodeQL analysis in CI; no known vulnerable dependencies |
@@ -224,6 +225,7 @@ sql-inliner is a .NET CLI tool and NuGet library that optimizes SQL Server views
224
225
| Version | Date | Reviewer | Changes |
225
226
|---------|------|----------|---------|
226
227
| v1 | 2026-02-28 | Claude Code (STRIDE analysis) | Initial threat model |
228
+
| v2 | 2026-02-28 | Claude Code | Mitigated I-1, I-2: Added built-in OS credential store |
0 commit comments