Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code execution concerns about PSGallery template distribution #224

Closed
daviwil opened this issue Dec 18, 2016 · 8 comments
Closed

Code execution concerns about PSGallery template distribution #224

daviwil opened this issue Dec 18, 2016 · 8 comments

Comments

@daviwil
Copy link
Contributor

daviwil commented Dec 18, 2016

@gerane was asking me yesterday if we are concerned about malicious code being executed if someone installs a Plaster template from the PSGallery. In our template distribution scheme we don't actually load the module to get the template metadata, we just use Get-Module -ListAvailable and pull the PSData out of the module object. The module itself could be loaded by the user in another way, though. The module which contains the template could have some malicious code which gets executed when auto-loaded in a PowerShell session.

Is this possibility enough of a concern for us to go even further and write our own set of Install/Find commands which can leverage the PSGallery for distribution but don't require the installation of a module in the PSModulePath? We could just use Save-Module then pull out the template files and put them in a user-level template folder.

@gerane
Copy link
Contributor

gerane commented Dec 18, 2016

I also hear the question a lot about how to find templates. Having find/install/publish wrappers might also help with that issue as well.

I wrote a few examples last night.

function Find-PlasterTemplate {
    [CmdletBinding(DefaultParameterSetName='Default')]
    [OutputType("PSCustomObject[]")]
    param(
        [Parameter(HelpMessage="Specifies the Name of the Plaster template to find",
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Name,

        [Parameter(HelpMessage="Specifies the minimum version of a single Template Version. The MaximumVersion and the RequiredVersion parameters are mutually exclusive; you cannot use both parameters in the same command.",
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='MinimumVersion')]
        [ValidateNotNull()]
        [Version]
        $MinimumVersion,

        [Parameter(HelpMessage="Specifies the maximum version of a single Template Version. The MaximumVersion and the RequiredVersion parameters are mutually exclusive; you cannot use both parameters in the same command.",
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='MaximumVersion')]
        [ValidateNotNull()]
        [Version]
        $MaximumVersion,

        [Parameter(HelpMessage="Specifies the exact version number of the Template to install",
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='RequiredVersion')]
        [ValidateNotNull()]
        [Version]
        $RequiredVersion,

        [Parameter(HelpMessage="Specifies to include all versions of a template in the results. You cannot use the AllVersions parameter with the MinimumVersion, MaximumVersion, or RequiredVersion parameters.",
                   ParameterSetName='RequiredVersion')]
        [switch]
        $AllVersions,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $Proxy,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [PSCredential]
        $ProxyCredential,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [PSCredential]
        $Credential
    )

    Process
    {
        Try {
            $Module = Find-Module @PSBoundParameters -Tag PlasterTemplate -ErrorAction SilentlyContinue

            if (!$Module) {
                Throw "No match was found for the specified search criteria and template name '${Name}'. Templates must have the tag 'PlasterTemplate'"
            }

            $Module
        }
        Catch {
            Throw
        }
    }
}


function Install-PlasterTemplate {
    [CmdletBinding(DefaultParameterSetName='Default',
                   SupportsShouldProcess=$true)]
    param(
        [Parameter(Mandatory=$true,
                   HelpMessage="Specifies the Name of the Plaster template to be installed",
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(HelpMessage="Specifies the minimum version of a single Template Version.",
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='MinimumVersion')]
        [ValidateNotNull()]
        [Version]
        $MinimumVersion,

        [Parameter(HelpMessage="Specifies the maximum version of a single Template Version.",
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='MaximumVersion')]
        [ValidateNotNull()]
        [Version]
        $MaximumVersion,

        [Parameter(HelpMessage="Specifies the exact version number of the Template to install",
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='RequiredVersion')]
        [ValidateNotNull()]
        [Version]
        $RequiredVersion,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $Proxy,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [PSCredential]
        $ProxyCredential,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [PSCredential]
        $Credential,

        [Parameter(HelpMessage="Forces the installation of the Template")]
        [switch]
        $Force
    )

    Begin
    {
        $YesToAll = $false
        $NoToAll = $false
    }

    Process
    {
        $PSBoundParameters['Repository'] = 'PSGallery'
        $PSBoundParameters['Tag'] = 'PlasterTemplate'

        $FindParams = $PSBoundParameters
        $Null = $FindParams.Remove('Force')
        $Null = $FindParams.Remove('Confirm')
        $Null = $FindParams.Remove('Whatif')

        Try {
            $TemplatesPath = "$Env:APPDATA\Plaster\Templates"
            $Module = PowerShellGet\Find-Module @PSBoundParameters

            if (! (Test-Path -Path $TemplatesPath)) {
                $Null = New-Item -Path $TemplatesPath -ItemType Directory
            }

            if (!$Module) {
                Throw "No match was found for the specified search criteria and template name '${Name}'. Templates must have the tag 'PlasterTemplate'"
            }
            else {
                $Destination = "$TemplatesPath\$($Module.Name)\$($Module.Version)"
            }
            if ($psCmdlet.ShouldProcess($Name, "Install Plaster template")) {
                if (!(Test-Path -Path $Destination)) {
                    $InstallTemplate = $true
                }
                elseif (!($YesToAll -OR $NoToAll -OR $Force)) {
                    $Message = "$Name already Exists"
                    $Message2 = "Do you want to install anyway?"
                    $InstallTemplate = $psCmdlet.ShouldContinue($Message2, $Message, [ref]$YesToAll, [ref]$NoToAll)
                }

                if ($InstallTemplate -or $YesToAll -or $Force) {
                    $PSBoundParameters["Force"] = $true
                    PowerShellGet\Save-Module -LiteralPath $TemplatesPath @PSBoundParameters
                }

            }
        }
        Catch {
            Throw
        }
    }
}

I began working on publish, but that one likely requires a little more input. A lot of questions about how to handle the psd1 for example. Since these aren't necessarily modules, if a psd1 doesn't exist should plaster create it for them and things like that. You could also bypass the publish-Module and use the Nuget publish Package directly for some more flexibility.

I was playing around with these and really liked this sort of idea. Only finds modules Tagged PlasterTemplate (or whatever tag preferred) and only installs if that tag is present as well. I just quickly chose appdata\plaster\templates, but could easily be something like $home.Plaster\templates etc.

I knew a wrapper around powershellget would allow using the psgallery backend, and be pretty easy to implement. I wanted to try it out so I the these together last night to try it out.

Very interested to see what others think or any ideas others might have.

@gerane
Copy link
Contributor

gerane commented Dec 18, 2016

I think a lot of people expect a scaffolding framework to be secure and trust that it won't execute code. Plaster has gone to great lengths to prevent code execution, and storing these in the path could potentially lead to some unwanted code being executed. This seemed like an easy way to get around this and be able to use the existing psgallery infrastructure. Using save-module to a custom path instead of install-module should remove most risks of the user uses the commands built in.

Then plaster can just search for the templates in the default template location.

@rkeithhill
Copy link
Collaborator

Unfortunately, as long as we use the PSGallery there is nothing to stop someone from executing Install-Module CoolPlasterTemplates; Import-Module CoolPlasterTemplates. You can only go so far to protect people from themselves. The question, is it worth the extra level of protection that Install-PlasterTemplate provides? I could either way on this one.

@Jaykul
Copy link

Jaykul commented Dec 18, 2016

The "problem" isn't that someone might Import-Module CoolPlasterTemplates ... the problem is that modules are autoloaded (and executed!!!) by PowerShell for the purpose of command discovery and tab completion. You shouldn't install a module without code-review unless you 100% trust the author(s), because that code can get invoked by PowerShell without you ever meaning to run it...

I agree with @gerane -- it should literally be as simple as putting the templates in a location that's not part of the $Env:PSModulePath.

To be honest, I strongly dislike "Modules" being used as the shipping mechanism for stuff that isn't PowerShell commands (everything from PackageManagement Providers to DSC Resources and PSScriptAnalyzer Rules and Pester tests, and now Plaster Templates). It pollutes the search results and confuses everyone.

@daviwil
Copy link
Contributor Author

daviwil commented Dec 18, 2016

One alternative I've considered is having a centralized GitHub repo which merely contains an index of Plaster template metadata pointing to other GitHub repos. That would allow us to write a OneGet provider which can pull tagged template releases straight from GitHub.

What other means could we use for template distribution?

@gerane
Copy link
Contributor

gerane commented Dec 18, 2016

@daviwil I was thinking about that as well. There would need to be a lot more code written up front to really get that off the ground, but might be better in the long run. The other question is how would people publish new templates and who is going to manage adding the new Templates to the Repository.

When I was looking at this before, I was also thinking about what you would need to write to manage the versioning. You could just piggy back off of the powershellget module's with the wrappers. You could just keep the metadata in package.json type configs. This is the metadata that the VSCode Gallery has for the PowerShell extension if you pull from their API.

{
    "publisher":  {
                      "publisherId":  "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee",
                      "publisherName":  "ms-vscode",
                      "displayName":  "Microsoft",
                      "flags":  "verified"
                  },
    "extensionId":  "40d39ce9-c381-47a0-80c8-a6661f731eab",
    "extensionName":  "PowerShell",
    "displayName":  "PowerShell",
    "flags":  "validated, public",
    "lastUpdated":  "2016-12-16T22:25:52.523Z",
    "publishedDate":  "2015-11-18T00:50:38.583Z",
    "releaseDate":  "2015-11-18T00:50:38.583Z",
    "shortDescription":  "Develop PowerShell scripts in Visual Studio Code!",
    "versions":  [
                     {
                         "version":  "0.8.0",
                         "flags":  "validated",
                         "lastUpdated":  "2016-12-16T22:25:52.58Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.8.0/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallerycdn.vsassets.io/extensions/ms-vscode/powershell/0.8.0/1481927151646"
                     },
                     {
                         "version":  "0.7.2",
                         "flags":  "validated",
                         "lastUpdated":  "2016-09-02T22:03:02.847Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.7.2/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallerycdn.vsassets.io/extensions/ms-vscode/powershell/0.7.2/1474455550053"
                     },
                     {
                         "version":  "0.7.1",
                         "flags":  "validated",
                         "lastUpdated":  "2016-08-24T22:52:34.017Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.7.1/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.7.1/assetbyname"
                     },
                     {
                         "version":  "0.7.0",
                         "flags":  "validated",
                         "lastUpdated":  "2016-08-18T15:33:50.793Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.7.0/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.7.0/assetbyname"
                     },
                     {
                         "version":  "0.6.2",
                         "flags":  "validated",
                         "lastUpdated":  "2016-08-12T18:47:03.013Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.6.2/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.6.2/assetbyname"
                     },
                     {
                         "version":  "0.6.1",
                         "flags":  "validated",
                         "lastUpdated":  "2016-05-16T23:46:24.013Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.6.1/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.6.1/assetbyname"
                     },
                     {
                         "version":  "0.6.0",
                         "flags":  "validated",
                         "lastUpdated":  "2016-05-12T19:28:21.337Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.6.0/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.6.0/assetbyname"
                     },
                     {
                         "version":  "0.5.0",
                         "flags":  "validated",
                         "lastUpdated":  "2016-03-10T22:20:22Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.5.0/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.5.0/assetbyname"
                     },
                     {
                         "version":  "0.4.1",
                         "flags":  "validated",
                         "lastUpdated":  "2016-02-17T21:05:41.647Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.4.1/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.4.1/assetbyname"
                     },
                     {
                         "version":  "0.4.0",
                         "flags":  "validated",
                         "lastUpdated":  "2016-02-10T00:04:12.257Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.4.0/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.4.0/assetbyname"
                     },
                     {
                         "version":  "0.3.1",
                         "flags":  "validated",
                         "lastUpdated":  "2015-12-17T20:55:10.433Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.3.1/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.3.1/assetbyname"
                     },
                     {
                         "version":  "0.3.0",
                         "flags":  "validated",
                         "lastUpdated":  "2015-12-16T15:11:46.633Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.3.0/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.3.0/assetbyname"
                     },
                     {
                         "version":  "0.2.0",
                         "flags":  "validated",
                         "lastUpdated":  "2015-11-23T22:46:30.54Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.2.0/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.2.0/assetbyname"
                     },
                     {
                         "version":  "0.1.0",
                         "flags":  "validated",
                         "lastUpdated":  "2015-11-18T01:23:49.91Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.1.0/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.1.0/assetbyname"
                     },
                     {
                         "version":  "0.0.1",
                         "flags":  "validated",
                         "lastUpdated":  "2015-11-18T00:50:38.583Z",
                         "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.0.1/assetbyname",
                         "fallbackAssetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.0.1/assetbyname"
                     }
                 ],
    "categories":  [
                       "Languages",
                       "Snippets",
                       "Linters",
                       "Debuggers"
                   ],
    "tags":  [
                 "debuggers",
                 "keybindings",
                 "powershell",
                 "snippet"
             ],
    "statistics":  [
                       {
                           "statisticName":  "install",
                           "value":  289899
                       },
                       {
                           "statisticName":  "averagerating",
                           "value":  4.263157844543457
                       },
                       {
                           "statisticName":  "ratingcount",
                           "value":  38
                       },
                       {
                           "statisticName":  "trendingdaily",
                           "value":  1.156003125271291
                       },
                       {
                           "statisticName":  "trendingmonthly",
                           "value":  9.982463755534335
                       },
                       {
                           "statisticName":  "trendingweekly",
                           "value":  3.1155265871927273
                       }
                   ],
    "deploymentType":  0,
    "Installs":  289899,
    "publisherName":  "ms-vscode",
    "FullName":  "ms-vscode.PowerShell",
    "Version":  "0.8.0",
    "assetUri":  "https://ms-vscode.gallery.vsassets.io/_apis/public/gallery/publisher/ms-vscode/extension/PowerShell/0.8.0/assetbyname"
}

You could probably use a lot of their format and just remove the unneeded if that was the route we wanted to go, but the way to submit new extensions and manage those is a bit more up in the air.

@lholman
Copy link

lholman commented Feb 3, 2017

I think @daviwil idea of a github repo and a Oneget provider is a sensible first step towards providing almost an approved collection of trusted templates. Code reviews as with all other PR's being required to get your template in to the repo.

@larssb
Copy link

larssb commented Jul 2, 2018

I created a project for containing Plaster templates named PlasterPlethora. I thought I might as well kick-start the community with this. Hopefully it can be a starter for getting us going with a way to share Plaster templates. I hope people will find it useful. I'm all ears for comments and feedback.

Kind regards.

@PowerShellOrg PowerShellOrg locked and limited conversation to collaborators Feb 26, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

7 participants