From 34080040a8c25dcabc719be4b530a28c66555025 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:58:37 +0000 Subject: [PATCH 1/5] Initial plan From f08e9092e364a32c0c7cd996484883a35bfe7afa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:05:41 +0000 Subject: [PATCH 2/5] Add genericWebhook configuration options to Deploy-CheckExtension.ps1 Co-authored-by: MWG-Logan <2997336+MWG-Logan@users.noreply.github.com> --- Task/Deploy-CheckExtension.ps1 | 42 ++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/Task/Deploy-CheckExtension.ps1 b/Task/Deploy-CheckExtension.ps1 index cf72ddd..4081091 100644 --- a/Task/Deploy-CheckExtension.ps1 +++ b/Task/Deploy-CheckExtension.ps1 @@ -113,6 +113,22 @@ Enable Debug Logging. Maps to "Enable Debug Logging" in Activity Log settings. A list of URLs that will completely bypass blocking. Entering **ANY** will decrease security on that website significantly. '@)][string[]]$urlAllowlist, + [Parameter(HelpMessage=@' +Enable Generic Webhook. Maps to "Generic Webhook Enabled" in webhook settings. +| State | Effect | +|-------|-----------------------------------------------------| +| `0` | Disabled (default) | +| `1` | Enabled (requires GenericWebhookUrl and Events) | +'@)][ValidateSet(0,1)][int]$EnableGenericWebhook = 0, + [Parameter(HelpMessage=@' +Generic Webhook URL. Required if EnableGenericWebhook=1. Blank by default. +'@)][string]$GenericWebhookUrl = '', + [Parameter(HelpMessage=@' +Generic Webhook Event Types. Array of event types to send to the webhook. +Valid values: detection_alert, false_positive_report, page_blocked, rogue_app_detected, threat_detected, validation_event +'@)][ValidateSet('detection_alert','false_positive_report','page_blocked','rogue_app_detected','threat_detected','validation_event')] + [string[]]$GenericWebhookEvents = @(), + [Parameter(HelpMessage=@' Branding: Company Name shown in extension UI. '@)][string]$CompanyName = 'CyberDrain', @@ -171,6 +187,9 @@ function Get-DesiredItem { [int]$UpdateInterval, [int]$EnableDebugLogging, [string[]]$urlAllowlist, + [int]$EnableGenericWebhook, + [string]$GenericWebhookUrl, + [string[]]$GenericWebhookEvents, [string]$CompanyName, [string]$CompanyUrl, [string]$ProductName, @@ -187,6 +206,7 @@ function Get-DesiredItem { foreach($b in $bases){ # Build canonical Present arrays once $brandingKey = Join-Path $b.ManagedKey 'customBranding' + $webhookKey = Join-Path $b.ManagedKey 'genericWebhook' $policyItems = @( @{ Path=$b.ManagedKey; Name='showNotifications'; Type='DWord'; Value=$ShowNotifications }, @{ Path=$b.ManagedKey; Name='enableValidPageBadge'; Type='DWord'; Value=$EnableValidPageBadge }, @@ -199,6 +219,11 @@ function Get-DesiredItem { @{ Path=$b.ManagedKey; Name='enableDebugLogging'; Type='DWord'; Value=$EnableDebugLogging } @{ Path=$b.ManagedKey; Name='urlAllowlist'; Type='MultiString'; Value=$urlAllowlist } ) + $webhookItems = @( + @{ Path=$webhookKey; Name='enabled'; Type='DWord'; Value=$EnableGenericWebhook }, + @{ Path=$webhookKey; Name='url'; Type='String'; Value=$GenericWebhookUrl }, + @{ Path=$webhookKey; Name='events'; Type='MultiString'; Value=$GenericWebhookEvents } + ) $brandingItems = @( @{ Path=$brandingKey; Name='companyName'; Type='String'; Value=$CompanyName }, @{ Path=$brandingKey; Name='companyURL'; Type='String'; Value=$CompanyUrl }, @@ -213,16 +238,17 @@ function Get-DesiredItem { ) if($Ensure -eq 'Present'){ - $policyItems + $brandingItems + $settingsItems | ForEach-Object { $_ } + $policyItems + $webhookItems + $brandingItems + $settingsItems | ForEach-Object { $_ } } else { # Transform for Absent: null policy & branding values, block extension, drop update_url $absentPolicy = $policyItems | ForEach-Object { @{ Path=$_.Path; Name=$_.Name; Type=$_.Type; Value=$null } } + $absentWebhook = $webhookItems | ForEach-Object { @{ Path=$_.Path; Name=$_.Name; Type=$_.Type; Value=$null } } $absentBranding = $brandingItems | ForEach-Object { @{ Path=$_.Path; Name=$_.Name; Type=$_.Type; Value=$null } } $absentSettings = @( @{ Path=$b.SettingsKey; Name='installation_mode'; Type='String'; Value='blocked' }, @{ Path=$b.SettingsKey; Name='update_url'; Type='Remove'; Value=$null } ) - $absentPolicy + $absentBranding + $absentSettings | ForEach-Object { $_ } + $absentPolicy + $absentWebhook + $absentBranding + $absentSettings | ForEach-Object { $_ } } } } @@ -234,6 +260,15 @@ if($EnableCippReporting -eq 1){ } } +if($EnableGenericWebhook -eq 1){ + if([string]::IsNullOrWhiteSpace($GenericWebhookUrl)){ + throw 'GenericWebhookUrl must be provided when EnableGenericWebhook=1.' + } + if($GenericWebhookEvents.Count -eq 0){ + throw 'At least one event type must be specified in GenericWebhookEvents when EnableGenericWebhook=1.' + } +} + # Build desired items once $desiredItems = Get-DesiredItem ` -Ensure $Ensure ` @@ -251,6 +286,9 @@ $desiredItems = Get-DesiredItem ` -UpdateInterval $UpdateInterval ` -EnableDebugLogging $EnableDebugLogging ` -urlAllowlist $urlAllowlist ` + -EnableGenericWebhook $EnableGenericWebhook ` + -GenericWebhookUrl $GenericWebhookUrl ` + -GenericWebhookEvents $GenericWebhookEvents ` -CompanyName $CompanyName ` -CompanyUrl $CompanyUrl ` -ProductName $ProductName ` From e591152bc207e7f0b7988c9b6656f8ea97386e1d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:50:03 +0000 Subject: [PATCH 3/5] Fix array type casting for MultiString registry values Co-authored-by: MWG-Logan <2997336+MWG-Logan@users.noreply.github.com> --- Task/Deploy-CheckExtension.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Task/Deploy-CheckExtension.ps1 b/Task/Deploy-CheckExtension.ps1 index 4081091..4074199 100644 --- a/Task/Deploy-CheckExtension.ps1 +++ b/Task/Deploy-CheckExtension.ps1 @@ -217,12 +217,12 @@ function Get-DesiredItem { @{ Path=$b.ManagedKey; Name='customRulesUrl'; Type='String'; Value=$CustomRulesUrl }, @{ Path=$b.ManagedKey; Name='updateInterval'; Type='DWord'; Value=$UpdateInterval }, @{ Path=$b.ManagedKey; Name='enableDebugLogging'; Type='DWord'; Value=$EnableDebugLogging } - @{ Path=$b.ManagedKey; Name='urlAllowlist'; Type='MultiString'; Value=$urlAllowlist } + @{ Path=$b.ManagedKey; Name='urlAllowlist'; Type='MultiString'; Value=([string[]]$urlAllowlist) } ) $webhookItems = @( @{ Path=$webhookKey; Name='enabled'; Type='DWord'; Value=$EnableGenericWebhook }, @{ Path=$webhookKey; Name='url'; Type='String'; Value=$GenericWebhookUrl }, - @{ Path=$webhookKey; Name='events'; Type='MultiString'; Value=$GenericWebhookEvents } + @{ Path=$webhookKey; Name='events'; Type='MultiString'; Value=([string[]]$GenericWebhookEvents) } ) $brandingItems = @( @{ Path=$brandingKey; Name='companyName'; Type='String'; Value=$CompanyName }, From dd82b50f2d0834d0a94631d9ab048d3b78f81eb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:19:04 +0000 Subject: [PATCH 4/5] Fix MultiString array handling to always return String[] type Co-authored-by: MWG-Logan <2997336+MWG-Logan@users.noreply.github.com> --- Task/Deploy-CheckExtension.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Task/Deploy-CheckExtension.ps1 b/Task/Deploy-CheckExtension.ps1 index 4074199..73d0eb3 100644 --- a/Task/Deploy-CheckExtension.ps1 +++ b/Task/Deploy-CheckExtension.ps1 @@ -127,7 +127,7 @@ Generic Webhook URL. Required if EnableGenericWebhook=1. Blank by default. Generic Webhook Event Types. Array of event types to send to the webhook. Valid values: detection_alert, false_positive_report, page_blocked, rogue_app_detected, threat_detected, validation_event '@)][ValidateSet('detection_alert','false_positive_report','page_blocked','rogue_app_detected','threat_detected','validation_event')] - [string[]]$GenericWebhookEvents = @(), + [string[]]$GenericWebhookEvents, [Parameter(HelpMessage=@' Branding: Company Name shown in extension UI. @@ -217,12 +217,12 @@ function Get-DesiredItem { @{ Path=$b.ManagedKey; Name='customRulesUrl'; Type='String'; Value=$CustomRulesUrl }, @{ Path=$b.ManagedKey; Name='updateInterval'; Type='DWord'; Value=$UpdateInterval }, @{ Path=$b.ManagedKey; Name='enableDebugLogging'; Type='DWord'; Value=$EnableDebugLogging } - @{ Path=$b.ManagedKey; Name='urlAllowlist'; Type='MultiString'; Value=([string[]]$urlAllowlist) } + @{ Path=$b.ManagedKey; Name='urlAllowlist'; Type='MultiString'; Value=([string[]]@($urlAllowlist)) } ) $webhookItems = @( @{ Path=$webhookKey; Name='enabled'; Type='DWord'; Value=$EnableGenericWebhook }, @{ Path=$webhookKey; Name='url'; Type='String'; Value=$GenericWebhookUrl }, - @{ Path=$webhookKey; Name='events'; Type='MultiString'; Value=([string[]]$GenericWebhookEvents) } + @{ Path=$webhookKey; Name='events'; Type='MultiString'; Value=([string[]]@($GenericWebhookEvents)) } ) $brandingItems = @( @{ Path=$brandingKey; Name='companyName'; Type='String'; Value=$CompanyName }, @@ -264,7 +264,7 @@ if($EnableGenericWebhook -eq 1){ if([string]::IsNullOrWhiteSpace($GenericWebhookUrl)){ throw 'GenericWebhookUrl must be provided when EnableGenericWebhook=1.' } - if($GenericWebhookEvents.Count -eq 0){ + if($null -eq $GenericWebhookEvents -or $GenericWebhookEvents.Count -eq 0){ throw 'At least one event type must be specified in GenericWebhookEvents when EnableGenericWebhook=1.' } } From b44a0256fb14d9e760212848b1483ebb916ae0e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:41:26 +0000 Subject: [PATCH 5/5] Use strongly typed String[] arrays to survive $using: scope transfer Co-authored-by: MWG-Logan <2997336+MWG-Logan@users.noreply.github.com> --- Task/Deploy-CheckExtension.ps1 | 42 ++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/Task/Deploy-CheckExtension.ps1 b/Task/Deploy-CheckExtension.ps1 index 73d0eb3..be87597 100644 --- a/Task/Deploy-CheckExtension.ps1 +++ b/Task/Deploy-CheckExtension.ps1 @@ -158,6 +158,44 @@ Write-Host "Starting enforcement for Extensions: Chrome=$ChromeExtensionId Edge= # [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter','')] param([string]$FutureParam) # Prefer explicit functional parameters over relying on script scope to satisfy analyzers & improve clarity. +function ConvertTo-StringArray { + <# + .SYNOPSIS + Safely converts input to a properly typed string array for registry MultiString values. + .DESCRIPTION + Ensures the returned value is always System.String[] type, never Object[]. + Handles null, empty arrays, and arrays with values correctly. + Uses [string[]] casting with explicit array conversion to ensure type survives $using: scope transfer. + #> + param([Parameter(ValueFromPipeline)]$InputArray) + + # If null, return empty string array with explicit type + if ($null -eq $InputArray) { + $emptyArray = [string[]]::new(0) + return ,$emptyArray + } + + # Ensure it's treated as an array + $arr = @($InputArray) + if ($arr.Count -eq 0) { + $emptyArray = [string[]]::new(0) + return ,$emptyArray + } + + # Use strongly typed array creation + $stringArray = [string[]]::new($arr.Count) + for ($i = 0; $i -lt $arr.Count; $i++) { + if ($null -ne $arr[$i]) { + $stringArray[$i] = [string]$arr[$i] + } else { + $stringArray[$i] = [string]::Empty + } + } + + # Return with unary comma to prevent unwrapping + return ,$stringArray +} + function Get-ManagedStorageBasePath { param( [string]$ChromeExtensionId, @@ -217,12 +255,12 @@ function Get-DesiredItem { @{ Path=$b.ManagedKey; Name='customRulesUrl'; Type='String'; Value=$CustomRulesUrl }, @{ Path=$b.ManagedKey; Name='updateInterval'; Type='DWord'; Value=$UpdateInterval }, @{ Path=$b.ManagedKey; Name='enableDebugLogging'; Type='DWord'; Value=$EnableDebugLogging } - @{ Path=$b.ManagedKey; Name='urlAllowlist'; Type='MultiString'; Value=([string[]]@($urlAllowlist)) } + @{ Path=$b.ManagedKey; Name='urlAllowlist'; Type='MultiString'; Value=(ConvertTo-StringArray $urlAllowlist) } ) $webhookItems = @( @{ Path=$webhookKey; Name='enabled'; Type='DWord'; Value=$EnableGenericWebhook }, @{ Path=$webhookKey; Name='url'; Type='String'; Value=$GenericWebhookUrl }, - @{ Path=$webhookKey; Name='events'; Type='MultiString'; Value=([string[]]@($GenericWebhookEvents)) } + @{ Path=$webhookKey; Name='events'; Type='MultiString'; Value=(ConvertTo-StringArray $GenericWebhookEvents) } ) $brandingItems = @( @{ Path=$brandingKey; Name='companyName'; Type='String'; Value=$CompanyName },