Skip to content

Common/Sanitizer: allow PowerShell data constructors @ { } [ ] - #22253

Closed
Vladimir Tretyakov (wawanawna) wants to merge 8 commits into
masterfrom
users/wawanawna/relax-ps-sanitizer-allow-special-characters
Closed

Common/Sanitizer: allow PowerShell data constructors @ { } [ ]#22253
Vladimir Tretyakov (wawanawna) wants to merge 8 commits into
masterfrom
users/wawanawna/relax-ps-sanitizer-allow-special-characters

Conversation

@wawanawna

@wawanawna Vladimir Tretyakov (wawanawna) commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Context

Customer-impact PR. PR #22171 wired the existing Common/Sanitizer/ArgumentsSanitizer.ps1 validator into the AzurePowerShellV2-V5 and ServiceFabricPowerShellV1 tasks behind two feature flags (AZP_75787_ENABLE_NEW_LOGIC org-level + DistributedTask.Tasks.EnableAzurePowerShellArgumentsSanitization per-pipeline). The regex allow-list in that validator does not include @, {, }, [, ], so customers using benign PowerShell data syntax in scriptArguments would hit PS_ScriptArgsSanitized the moment the per-org FF is enabled — identical to the regression Ivan just fixed for the AzureCLI side in #22249 (issue #22173).

Mirrors:

Related context:


Task Name

Common/Sanitizer plus version bumps on all 17 consumer tasks: AzureFileCopyV1-V6 (6), AzurePowerShellV2-V5 (4), PowerShellOnTargetMachinesV3, PowerShellV2, ServiceFabricPowerShellV1, SqlAzureDacpacDeploymentV1, SqlDacpacDeploymentOnMachineGroupV0, WindowsMachineFileCopyV1/V2 (2) — matches the consumer set bumped in PR #22171.


Description

Tasks/Common/Sanitizer/ArgumentsSanitizer.ps1: the negated character class now also allows @, {, }, [, ]. Concretely:

  • Before: '(?<!)([^\w\ _''"\-=\/:\.*,+~?%\n#])(?!true|false)'
  • After: '(?<!)([^\w\ _''"\-=\/:\.*,+~?%\n#@{}\[\]])(?!true|false)'

These five characters by themselves cannot construct an executable PowerShell expression. Attack primitives stay blocked: ;, &, |, $, (, ), and unescaped backtick. Mirrors Ivan's TS regex change in Tasks/AzureCLIV{2,3}/src/argsSanitizer.ts exactly.

Customer-visible: -Tag @{...}, multi-line hashtables, splat @params, type accelerator [string], literal index [0], and hashtables that quote $env:Foo substitutions now pass when the FF is on. Without this change the same customer-impact wave the AzureCLI side just took would land on PowerShell tasks the moment the org FF flips.


Risk Assessment (Low / Medium / High)

Low. Strictly relaxes the allow-list — no new characters become executable on their own, and every attack primitive (; & | $ ( ) and unescaped backtick) is still blocked. Behavior when the FF is off is unchanged. Identity-preservation tests confirm Get-SanitizedArgumentsArray does not mangle the newly-allowed input.


Change Behind Feature Flag (Yes / No)

Yes. Reuses the existing two-flag gating from PR #22171: AZP_75787_ENABLE_NEW_LOGIC (org) and DistributedTask.Tasks.EnableAzurePowerShellArgumentsSanitization (per-pipeline). This PR does not change FF wiring — only the regex inside the validator that runs once both flags are On.


Tech Design / Approach

The TS-side fix in #22249 is the reference implementation. The PS regex now allows the same set of characters Ivan added on the TS side, so customer-facing behavior is symmetric across language flavors of the task family. Alternative considered: allow only @ and { } (covers @{...} and splat) but reject [ ]. Rejected — [string] type accelerator and literal index [0] are equally idiomatic in scriptArguments, and once $ ( ) are blocked the index/accelerator cannot reach an evaluator.


Documentation Changes Required (Yes/No)

No.


Unit Tests Added or Updated (Yes / No)

Yes.

  • New file Tasks/Common/Sanitizer/Tests/L0Protect-ScriptArguments.AllowsDataConstructors.ps1 — 7 positive cases covering hashtable, multi-line hashtable, splat @params, type accelerator [string], literal index [0], hashtable with email value, and hashtable with $env: substitution. Uses $test.Input correctly (sibling Passes.ps1 has a latent $inputArgs typo that silently no-ops the loop — out of scope to fix here).
  • Tasks/Common/Sanitizer/Tests/L0Protect-ScriptArguments.Throws.ps1 — +5 must-still-throw cases combining allowed data syntax with attack primitives: hashtable hiding $(Get-Date), $(rm -rf /), -Cmd & evil.exe, array literal @("a","b","c"), hashtable with ; separator.
  • Tasks/Common/Sanitizer/Tests/L0Get-SanitizedArgumentsArray.DoesNotBreakExistingPowerShellFormats.ps1 — +4 identity-preservation cases for hashtable, splat, type accelerator, index.
  • L0.ts wired the new file as a separate it() block.

Result: full Common/Sanitizer L0 suite (9 it() blocks) passes locally on Node 22.17.1 / mocha.


Additional Testing Performed

  • Direct regex sanity check on Protect-ScriptArguments with Get-VstsLocString / Write-VstsTaskWarning mocked: 13/13 hand-picked cases pass — 5 positive (hashtable, splat, [string], [0], hashtable with @ in value) + 8 negative (semi, &&, $(), |, hashtable+subexpr, hashtable+;, array literal @(..), & call).
  • Full mocha L0 suite for Tasks/Common/Sanitizer/Tests run end-to-end (build via node make.js build --task WindowsMachineFileCopyV1, then mocha ./L0.js): 9 passing.
  • Sandbox repro on tfspf-rmpf/MSRCTest for the original MSRC vector (ICM 115118): confirmed the sanitizer (when the FF is enabled) blocks the attack inputs unchanged.

Logging Added/Updated (Yes/No)

No.


Telemetry Added/Updated (Yes/No)

No. The existing sanitizerThrew / OrgLevelFeatureFlag telemetry continues to fire unchanged.


Rollback Scenario and Process (Yes/No)

Yes. The per-org AZP_75787_ENABLE_NEW_LOGIC and per-pipeline DistributedTask.Tasks.EnableAzurePowerShellArgumentsSanitization flags can disable the entire sanitizer if a regression emerges. A regex revert in Tasks/Common/Sanitizer/ArgumentsSanitizer.ps1 is a one-line change. Patch-only version bumps mean the task minors do not move.


Dependency Impact Assessed and Regression Tested (Yes/No)

Yes. Same impact set as PR #22171: 17 consumer tasks listed under Task Name. All are bumped here in lockstep (Patch +1). _generated/ mirrors for AzureFileCopyV4/V5/V6 and AzurePowerShellV4/V5 (Default + _Node24 variants) plus their .versionmap.txt files are bumped in lockstep with their non-_generated/ source. No public API contract changes.


Checklist

The PowerShell ArgumentsSanitizer regex blocked benign data syntax
(`-Tag @{...}`, splat `@params`, type accelerator `[string]`, literal
index `[0]`) that customers commonly pass via scriptArguments.

PR #22171 wired this sanitizer into AzurePowerShellV2-V5 and
ServiceFabricPowerShellV1 behind two FFs
(AZP_75787_ENABLE_NEW_LOGIC + DistributedTask.Tasks.EnableAzure
PowerShellArgumentsSanitization). Once the per-org FF flips on,
every pipeline using the syntax above would fail with
PS_ScriptArgsSanitized - exactly the regression Ivan just fixed
for AzureCLI in PR #22249 (issue #22173).

Adds `@ { } [ ]` to the allowed character class. Attack primitives
remain blocked: `; & | $ ( )` and unescaped backtick. Mirrors Ivan's
TS-side regex change in Tasks/AzureCLIV{2,3}/src/argsSanitizer.ts
exactly.

Tests:
- New L0Protect-ScriptArguments.AllowsDataConstructors.ps1 (7 cases:
  hashtable, multi-line hashtable, splat, type accelerator, index,
  hashtable with email, hashtable with $env: substitution).
- L0Protect-ScriptArguments.Throws.ps1: +5 must-still-throw cases
  combining allowed data syntax with attack primitives (hashtable
  hiding $(Get-Date), $(rm -rf /), `& evil.exe`, array literal
  `@(...)`, hashtable with `;` separator).
- L0Get-SanitizedArgumentsArray.DoesNotBreakExistingPowerShellFormats:
  +4 identity-preservation cases confirming Get-SanitizedArgumentsArray
  doesn't mangle the newly-allowed syntax.
- L0.ts wired the new file as a separate it() block.
- All 9 L0 suites pass locally (mocha 3s).

Bumps Patch on all 17 consumer tasks + 5 _generated mirrors per the
PR #22171 precedent (AzureFileCopyV1-V6, AzurePowerShellV2-V5,
PowerShellOnTargetMachinesV3, PowerShellV2, ServiceFabricPowerShellV1,
SqlAzureDacpacDeploymentV1, SqlDacpacDeploymentOnMachineGroupV0,
WindowsMachineFileCopyV1/V2).

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

Updates the shared PowerShell scriptArguments sanitizer in Tasks/Common/Sanitizer to allow common PowerShell data-constructor syntax characters (@ { } [ ]) so benign hashtables/splatting/type accelerators don’t get rejected when the sanitizer feature flags are enabled, and rolls the change out via patch version bumps to all consumer tasks (including _generated mirrors and version maps).

Changes:

  • Relax ArgumentsSanitizer.ps1 allowlist regex to permit @ { } [ ] in non-execution contexts.
  • Add/extend L0 tests to cover allowed data-constructors and must-still-block attack primitives.
  • Patch-bump all sanitizer consumers (and update _generated/* + *.versionmap.txt where applicable).

Reviewed changes

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

Show a summary per file
File Description
Tasks/Common/Sanitizer/ArgumentsSanitizer.ps1 Allowlist regex updated to permit @ { } [ ]; inline documentation updated.
Tasks/Common/Sanitizer/Tests/L0Protect-ScriptArguments.AllowsDataConstructors.ps1 New L0 test file covering permitted data-constructor patterns.
Tasks/Common/Sanitizer/Tests/L0Protect-ScriptArguments.Throws.ps1 Adds negative cases ensuring execution primitives remain blocked.
Tasks/Common/Sanitizer/Tests/L0Get-SanitizedArgumentsArray.DoesNotBreakExistingPowerShellFormats.ps1 Adds identity-preservation cases for newly allowed patterns.
Tasks/Common/Sanitizer/Tests/L0.ts Registers the new L0 test file in the mocha suite.
Tasks/AzureFileCopyV1/task.json Patch version bump (deprecated task).
Tasks/AzureFileCopyV1/task.loc.json Patch version bump.
Tasks/AzureFileCopyV2/task.json Patch version bump (deprecated task).
Tasks/AzureFileCopyV2/task.loc.json Patch version bump.
Tasks/AzureFileCopyV3/task.json Patch version bump (deprecated task).
Tasks/AzureFileCopyV3/task.loc.json Patch version bump.
Tasks/AzureFileCopyV4/task.json Patch version bump.
Tasks/AzureFileCopyV4/task.loc.json Patch version bump.
Tasks/AzureFileCopyV5/task.json Patch version bump.
Tasks/AzureFileCopyV5/task.loc.json Patch version bump.
Tasks/AzureFileCopyV6/task.json Patch version bump.
Tasks/AzureFileCopyV6/task.loc.json Patch version bump.
Tasks/AzurePowerShellV2/task.json Patch version bump (deprecated task).
Tasks/AzurePowerShellV2/task.loc.json Patch version bump.
Tasks/AzurePowerShellV3/task.json Patch version bump (deprecated task).
Tasks/AzurePowerShellV3/task.loc.json Patch version bump.
Tasks/AzurePowerShellV4/task.json Patch version bump.
Tasks/AzurePowerShellV4/task.loc.json Patch version bump.
Tasks/AzurePowerShellV5/task.json Patch version bump.
Tasks/AzurePowerShellV5/task.loc.json Patch version bump.
Tasks/PowerShellV2/task.json Patch version bump.
Tasks/PowerShellV2/task.loc.json Patch version bump.
Tasks/PowerShellOnTargetMachinesV3/task.json Patch version bump.
Tasks/PowerShellOnTargetMachinesV3/task.loc.json Patch version bump.
Tasks/ServiceFabricPowerShellV1/task.json Patch version bump.
Tasks/ServiceFabricPowerShellV1/task.loc.json Patch version bump.
Tasks/SqlAzureDacpacDeploymentV1/task.json Patch version bump.
Tasks/SqlAzureDacpacDeploymentV1/task.loc.json Patch version bump.
Tasks/SqlDacpacDeploymentOnMachineGroupV0/task.json Patch version bump.
Tasks/SqlDacpacDeploymentOnMachineGroupV0/task.loc.json Patch version bump.
Tasks/WindowsMachineFileCopyV1/task.json Patch version bump.
Tasks/WindowsMachineFileCopyV1/task.loc.json Patch version bump.
Tasks/WindowsMachineFileCopyV2/task.json Patch version bump.
Tasks/WindowsMachineFileCopyV2/task.loc.json Patch version bump.
_generated/AzureFileCopyV4/task.json Mirror patch version bump.
_generated/AzureFileCopyV4/task.loc.json Mirror patch version bump.
_generated/AzureFileCopyV4.versionmap.txt Version map updated.
_generated/AzureFileCopyV4_Node24/task.json Node24 mirror patch version bump.
_generated/AzureFileCopyV4_Node24/task.loc.json Node24 mirror patch version bump.
_generated/AzureFileCopyV5/task.json Mirror patch version bump.
_generated/AzureFileCopyV5/task.loc.json Mirror patch version bump.
_generated/AzureFileCopyV5.versionmap.txt Version map updated.
_generated/AzureFileCopyV5_Node24/task.json Node24 mirror patch version bump.
_generated/AzureFileCopyV5_Node24/task.loc.json Node24 mirror patch version bump.
_generated/AzureFileCopyV6/task.json Mirror patch version bump.
_generated/AzureFileCopyV6/task.loc.json Mirror patch version bump.
_generated/AzureFileCopyV6.versionmap.txt Version map updated.
_generated/AzureFileCopyV6_Node24/task.json Node24 mirror patch version bump.
_generated/AzureFileCopyV6_Node24/task.loc.json Node24 mirror patch version bump.
_generated/AzurePowerShellV4/task.json Mirror patch version bump.
_generated/AzurePowerShellV4/task.loc.json Mirror patch version bump.
_generated/AzurePowerShellV4.versionmap.txt Version map updated.
_generated/AzurePowerShellV4_Node24/task.json Node24 mirror patch version bump.
_generated/AzurePowerShellV4_Node24/task.loc.json Node24 mirror patch version bump.
_generated/AzurePowerShellV5/task.json Mirror patch version bump.
_generated/AzurePowerShellV5/task.loc.json Mirror patch version bump.
_generated/AzurePowerShellV5.versionmap.txt Version map updated.
_generated/AzurePowerShellV5_Node24/task.json Node24 mirror patch version bump.
_generated/AzurePowerShellV5_Node24/task.loc.json Node24 mirror patch version bump.

Comment thread Tasks/Common/Sanitizer/ArgumentsSanitizer.ps1 Outdated
Comment thread Tasks/Common/Sanitizer/ArgumentsSanitizer.ps1 Outdated
Comment thread Tasks/Common/Sanitizer/Tests/L0Protect-ScriptArguments.AllowsDataConstructors.ps1 Outdated
Comment thread Tasks/AzureFileCopyV1/task.json
Comment thread Tasks/AzureFileCopyV2/task.json
Comment thread Tasks/AzureFileCopyV3/task.json
Comment thread Tasks/AzurePowerShellV2/task.json
Comment thread Tasks/AzurePowerShellV3/task.json
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@wawanawna

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

…d task versions

CI failure: BuildConfigGen verify step rejected the previous commit because the inner
`_buildConfigMapping` block at the bottom of each generated `task.json` / `task.loc.json`
still referenced the master baseline versions (`.0` for Default, `.1` for Node24_1).
The top-level `version` field was already bumped in the original PR, but the embedded
mapping block was missed for the 5 families that have a Node24 build config.

Reproduced locally with:
  dotnet run --no-launch-profile --project BuildConfigGen/BuildConfigGen.csproj --task `...` --write-updates
Re-verify (no --write-updates) now exits 0.

Affected families (Default + Node24 mirrors of each):
  - AzureFileCopyV4 / V5 / V6
  - AzurePowerShellV4 / V5

20 files changed; only the inner `Default` / `Node24_1` mapping entries shift +1.
No source task changes, no behaviour changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@wawanawna

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

CI failure: `Check for downgrading tasks` step rejected the previous bump
because the rule is `new Default > max(all previous configs)`, not just
`> previous Default`. For families with a Node24_1 mirror, the prior max
was Default+1, so source bump +1 left new Default equal to prior Node24_1.

Fix: bump source `Tasks/<X>/task.json` and `task.loc.json` Patch `.1 -> .3`
for the 5 affected families. BuildConfigGen regenerates Default=.3 / Node24_1=.4,
both strictly above prior max .1, satisfying both `Verify generated files`
and `Check for downgrading tasks`.

Affected families:
  - AzureFileCopyV4 / V5 / V6
  - AzurePowerShellV4 / V5

35 files changed: 10 source + 5 versionmap + 20 generated. No behavioural changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@wawanawna

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

Walks back the previous over-bump. The downgrade check in ci/check-downgrading.js
requires only `new > master max`, not `master max + 2`. For these 5 families
where prior master had Default=.276.0 and Node24_1=.276.1 (so max=.276.1),
the minimum valid bump is Default=.276.2 / Node24_1=.276.3 — not .276.3/.276.4.

Source `Tasks/<X>/{task.json,task.loc.json}` Patch 3 -> 2.
Generated files and versionmap updated in lockstep.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@wawanawna

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

Comment thread _generated/AzureFileCopyV4/task.loc.json
@wawanawna

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 3 pipeline(s).

Vladimir Tretyakov (wawanawna) pushed a commit that referenced this pull request Jun 17, 2026
PR #22253 proposed relaxing the PowerShell argument-sanitizer allow-list to
permit the data-constructor characters @ { } [ ] (regression issue #22173) so
legitimate hashtable/array arguments to AzurePowerShell and ServiceFabricPowerShell
scripts are no longer mangled. That character-only relaxation, however, re-opens
code execution: the sanitizer only *validates* arguments - the task runs the RAW
string verbatim at a dot-source sink (". '<script>' <args>" /
"& '<script>' <args>"), so an argument such as

    -Tag @{ k = New-Item -Path C:\evil.txt -ItemType File -Force }
    -Tag @{ k = $(whoami) }
    -Tag @{ k = [adsi]'LDAP://attacker' }

uses only allow-listed characters yet EXECUTES, because a hashtable value, array
element, sub-expression or cast inside a data constructor is an evaluated
expression at bind time. (The same tokens at top-level argument position are
inert literal strings at this sink - verified empirically against both real
sinks.) PR #22249 already shipped the equivalent relaxation for AzureCLI and
carries the same exposure.

Fix: keep the relaxed character allow-list but add a structural AST backstop,
Test-SanitizerArgumentAst, that parses the raw arguments exactly as the sink
does and rejects anything that is not a pure data literal - parse errors, nested
commands, script blocks, member access / method calls and type casts - while
still allowing @{ Port = 8080 }, @{ Owner = "a@b.com" }, splatting, $env:
variables, quoted strings and numbers.

The relaxed path is opt-in via a new -AllowDataConstructors switch on
Get-SanitizedArguments / Protect-ScriptArguments. Only the dispatcher
(Invoke-ScriptArgumentSanitization, used by AzurePowerShellV2-V5 and
ServiceFabricPowerShellV1) passes it. Legacy direct callers (AzureFileCopy,
PowerShell, PowerShellOnTargetMachines, WindowsMachineFileCopy, Sql*Deployment)
keep the strict allow-list with no behavioral change (the switch defaults off).

L0 coverage added:
- Test-SanitizerArgumentAst classification (data literals vs. executable exprs)
- relaxed path allows legitimate data constructors
- relaxed path blocks data-constructor injection, with an explicit assertion
  that the character regex alone does NOT catch them (the AST is load-bearing)
- strict (legacy) path is unchanged for non-dispatcher callers

Version-bumped all 17 sanitizer-bundling tasks and regenerated _generated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@wawanawna
Vladimir Tretyakov (wawanawna) marked this pull request as draft June 19, 2026 13:01
Vladimir Tretyakov (wawanawna) added a commit that referenced this pull request Jun 23, 2026
…22173, supersedes #22253) (#22273)

* Harden PowerShell argument sanitizer against data-constructor RCE

PR #22253 proposed relaxing the PowerShell argument-sanitizer allow-list to
permit the data-constructor characters @ { } [ ] (regression issue #22173) so
legitimate hashtable/array arguments to AzurePowerShell and ServiceFabricPowerShell
scripts are no longer mangled. That character-only relaxation, however, re-opens
code execution: the sanitizer only *validates* arguments - the task runs the RAW
string verbatim at a dot-source sink (". '<script>' <args>" /
"& '<script>' <args>"), so an argument such as

    -Tag @{ k = New-Item -Path C:\evil.txt -ItemType File -Force }
    -Tag @{ k = $(whoami) }
    -Tag @{ k = [adsi]'LDAP://attacker' }

uses only allow-listed characters yet EXECUTES, because a hashtable value, array
element, sub-expression or cast inside a data constructor is an evaluated
expression at bind time. (The same tokens at top-level argument position are
inert literal strings at this sink - verified empirically against both real
sinks.) PR #22249 already shipped the equivalent relaxation for AzureCLI and
carries the same exposure.

Fix: keep the relaxed character allow-list but add a structural AST backstop,
Test-SanitizerArgumentAst, that parses the raw arguments exactly as the sink
does and rejects anything that is not a pure data literal - parse errors, nested
commands, script blocks, member access / method calls and type casts - while
still allowing @{ Port = 8080 }, @{ Owner = "a@b.com" }, splatting, $env:
variables, quoted strings and numbers.

The relaxed path is opt-in via a new -AllowDataConstructors switch on
Get-SanitizedArguments / Protect-ScriptArguments. Only the dispatcher
(Invoke-ScriptArgumentSanitization, used by AzurePowerShellV2-V5 and
ServiceFabricPowerShellV1) passes it. Legacy direct callers (AzureFileCopy,
PowerShell, PowerShellOnTargetMachines, WindowsMachineFileCopy, Sql*Deployment)
keep the strict allow-list with no behavioral change (the switch defaults off).

L0 coverage added:
- Test-SanitizerArgumentAst classification (data literals vs. executable exprs)
- relaxed path allows legitimate data constructors
- relaxed path blocks data-constructor injection, with an explicit assertion
  that the character regex alone does NOT catch them (the AST is load-bearing)
- strict (legacy) path is unchanged for non-dispatcher callers

Version-bumped all 17 sanitizer-bundling tasks and regenerated _generated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Block -as conversion operator and bare type references in AST backstop

Independent security review found a bypass: an -as conversion such as
@{ k = 'C:\victim\file.ps1' -as [System.IO.StreamWriter] } uses only
allow-listed characters and parses as a BinaryExpressionAst + TypeExpressionAst
(neither flagged by the previous predicate), but still invokes the target type's
constructor at the dot-source sink - verified to execute with both a [type]
literal and a string/variable right operand, on both the AzurePowerShell and
ServiceFabric sinks under PowerShell 5.1 and 7.

Test-SanitizerArgumentAst now also rejects:
- the -as operator (BinaryExpressionAst with TokenKind.As), regardless of the
  right-operand form, which also makes the [type]'x' cast and 'x' -as [type]
  conversion forms consistently blocked; and
- any TypeExpressionAst (a type reference used as a value inside a data
  constructor); top-level type literals passed as plain arguments do not parse
  as TypeExpressionAst and remain allowed (e.g. -Type [System.String]).

L0: added -as (type-literal and value-type) and bare-type-reference cases to the
classifier and integration injection corpora; -Type [System.String] added to the
allow corpus. Full Sanitizer L0 suite passes (13/13).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Clarify comments: @(...) array literals stay blocked (parens not allowed)

* Polish sanitizer comments: strict vs relaxed modes, two task groups, unify goal

* Refactor: shared regex base + module-level dangerous AST type list

* Revert to two explicit regex literals for readability

* Bump AzurePowerShellV4/V5 above master (resolve common-change downgrade) + fix typo

* Fix AzurePowerShellV4 task.json version (racy-clean miss in e64787a: .2->.4 same byte-length)

The previous commit updated V4's _generated versionmap to 4.276.4 but git's
stat cache missed the same-byte-length task.json edit (.2->.4), leaving the
committed task.json at 4.276.2. BuildConfigGen rejected the inconsistency
(inputVersion 4.276.2 <= maxVersion 4.276.5, != defaultVersion 4.276.4).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restore AZP_75787_ENABLE_NEW_LOGIC after data-constructor L0 tests

Addresses review feedback: the two new L0 tests set the
AZP_75787_ENABLE_NEW_LOGIC feature-flag env var at load time but never
restored it. The L0 PSRunner reuses a single powershell.exe process
across test scripts, so the unrestored value leaked into subsequent
tests. Capture the original value and restore it (or remove it when
originally unset) in a finally block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restore AZP_75787_ENABLE_NEW_LOGIC in pre-existing Passes/Throws L0 tests

Same env-var leak as the new data-constructor tests: these two
pre-existing siblings set AZP_75787_ENABLE_NEW_LOGIC at load time and
never restored it, leaking 'true' into subsequent tests through the
shared PSRunner process. Wrap them in the same try/finally restore so
the full Sanitizer L0 suite runs leak-free.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Uladzimir Tratsiakou (Vladimir/Vova) <utratsiakou@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants