chore(m365): use PowerShell best practices for quoting credential variables#9997
Conversation
|
✅ Conflict Markers Resolved All conflict markers have been successfully resolved in this pull request. |
|
Hello @sandiyochristan! Thanks for this contribution, we'll review and test it once we have time 🙌 |
HugoPBrito
left a comment
There was a problem hiding this comment.
Hey @sandiyochristan, thanks for the contribution!
The core idea here is solid. You're right that double quotes in PowerShell expand variables and that's a real problem for secrets. Let me break down what we'd need to land this.
✅ What works (with small tweaks)
-
client_secret→ single quotes (line 139): This is the real win of the PR. A legitimate secret likePa$$w0rdsilently gets corrupted today because PowerShell expands$$. Single quotes with the'→''escape is the correct PowerShell convention. One important note: don't useself.sanitize()on the secret — its regex strips chars like$,!,#that are perfectly valid in secrets. -
client_id/tenant_id→ addself.sanitize()in Application Auth (lines 138-140): The Certificate Auth path already does this (lines 118-119), Application Auth should be consistent. These areUUIDssosanitize()is safe here. Switch to single quotes too for uniformity. -
Remove double quotes around
$clientSecretintest_exchange_connection(line 199). It's already aPSvariable at that point, wrapping it in"..."is redundant and could re-expand special chars that were correctly handled during assignment.
❌ Please remove
The security-best-practices.mdx doc — We appreciate the thought, but it's generic content that doesn't follow the project's documentation style. Let's keep this PR focused on the actual code fix.
📝 What's missing to merge
- Update existing tests —
test_init_credentialassertions still expect the old double-quote format, they'll fail with these changes. - Add a test for special chars — Something like a secret with
Pa$$w0rd!#'test to prove the escape logic actually works. That's the whole point of this fix, we should have a test covering it.
A note on the framing
The PR describes this as a command injection fix, that's overstated. Before any credential reaches PowerShell, validate_static_credentials() authenticates against Azure AD via MSAL. A malicious secret would fail auth and never get here.
The real value of this change is correctness: preventing silent corruption of legitimate secrets that happen to contain special characters. That's still worth fixing, just want to set the right expectations.
Let us know if you have questions, happy to help with the test cases!
|
Thanks for the detailed review @HugoPBrito! All feedback addressed in the latest commit: Done:
Re: framing — Agreed, this is a correctness fix (preventing silent corruption of legitimate secrets with special chars), not a command injection fix. Updated the PR title accordingly. |
Guard against NoneType error when client_secret is None (certificate auth tests), and add SDK CHANGELOG entry for PR prowler-cloud#9997. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## master #9997 +/- ##
==========================================
- Coverage 93.97% 87.89% -6.08%
==========================================
Files 237 133 -104
Lines 34829 5724 -29105
==========================================
- Hits 32729 5031 -27698
+ Misses 2100 693 -1407
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
HugoPBrito
left a comment
There was a problem hiding this comment.
Thanks for the updates. I only see two small follow-ups left:
- Please strengthen the Exchange test coverage by asserting the exact command used for the secure secret conversion, especially
ConvertTo-SecureString $clientSecret -AsPlainText -Force, so we catch any regression back to"$clientSecret". - Please update the
init_credential()docstring/note to match the current behavior.client_idandtenant_idare sanitized,
butclient_secretis intentionally not sanitized anymore and is only single-quote escaped to preserve valid special characters.
After that I'll functionally test the PR in depth to ensure it's working as expected.
Sanitize client_secret in M365PowerShell to use single quotes, preventing command injection and variable expansion issues with special characters. Also added docs/developer-guide/security-best-practices.mdx
- Remove security-best-practices.mdx (out of scope) - Add self.sanitize() to client_id/tenant_id in Application Auth path for consistency with Certificate Auth path - Switch client_id/tenant_id to single quotes for uniformity - Update test assertions to match new single-quote format - Add test_init_credential_special_chars_in_secret covering Pa$$w0rd!#' to prove the escape logic preserves $, !, # and escapes ' Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Guard against NoneType error when client_secret is None (certificate auth tests), and add SDK CHANGELOG entry for PR prowler-cloud#9997. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…redential docstring Add an assert_any_call for the exact "ConvertTo-SecureString $clientSecret -AsPlainText -Force" command in test_test_exchange_connection_success so a regression back to the double-quoted "$clientSecret" form is caught. Update the init_credential() docstring Note to match current behavior: client_id and tenant_id are sanitized via sanitize(), while client_secret is intentionally not sanitized (to preserve valid special characters) and only single-quote escaped to prevent PowerShell variable expansion.
06933e9 to
2b4d58e
Compare
Add a parametrized test exercising the single-quote escaping of client_secret
for the cases that motivated the fix and could otherwise break:
- $ and ${...} and $(...) must stay literal (no PowerShell expansion)
- backtick, double quotes, semicolons and other metacharacters are preserved
- single quotes are doubled per PowerShell convention
- an injection-style payload stays a single literal string, cannot break out
- empty and None secrets render an empty single-quoted string
Also assert client_id and tenant_id are sanitized in the Application Auth
path, stripping shell metacharacters while keeping UUID-safe characters.
|
Pushed the follow-ups and verified the fix.
|
|
Reframed this PR as a best practices improvement rather than a security fix. What changed: the M365 PowerShell session now assigns credential variables (client ID, tenant ID, client secret) using single-quoted strings, the recommended PowerShell convention for literal values. Single quotes treat the content verbatim, so there is no variable interpolation or subexpression evaluation, and embedded single quotes are escaped as On scope: Microsoft auto-generated Entra client secrets only use the character set Reference: https://learn.microsoft.com/en-us/purview/sit-defn-azure-ad-client-secret |
…es change Reframe the M365 PowerShell single-quote change as a best-practices improvement (Changed) rather than a fix, and place it under the 5.28.0 Changed section.
Reword the init_credential() docstring, the inline secret-handling comment and the edge-case test wording to describe single-quoting as the PowerShell convention for literal values, removing the inaccurate claim that client secrets contain characters like $, ! or # and the command-injection framing.
Description
Adopt PowerShell quoting best practices for credential variables in the M365 provider's PowerShell session. Literal values such as the client ID, tenant ID and client secret are now assigned with single-quoted strings instead of double-quoted ones, which is the recommended convention for literals in PowerShell: single quotes treat the content verbatim and avoid unintended variable interpolation or subexpression evaluation. Embedded single quotes are escaped following the standard
'to''rule.This is a general robustness and consistency improvement to how credentials are handled in the PowerShell layer, aligning the Application Auth path with the Certificate Auth path.
Checklist