Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Engine.Tests/Analyze/Conditions/LanguageModeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Engine.Analyze.Conditions;
using Xunit;

namespace Engine.Tests.Analyze.Conditions
{
public class LanguageModeTests
{
[Fact]
public void ShouldMatchConstrainedLanguageMode()
{
var condition = new LanguageModeCondition();

var result = condition.Analyze(new ScriptContext { LanguageMode = "ConstrainedLanguage" }, new Configuration.Condition
{
Property = "languagemode",
Operator = "equals",
Value = "ConstrainedLanguage"
});

Assert.True(result);
}

[Fact]
public void ShouldMatchAFullLanguageViolation()
{
var condition = new LanguageModeCondition();

var result = condition.Analyze(new ScriptContext { LanguageMode = "FullLanguage" }, new Configuration.Condition
{
Property = "languagemode",
Operator = "notequals",
Value = "ConstrainedLanguage"
});

Assert.True(result);
}
}
}
1 change: 1 addition & 0 deletions PowerShellProtect/Analyze/Analyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public Analyzer()
new DomainControllerCondition(),
new ComputerNameCondition(),
new DomainCondition(),
new LanguageModeCondition(),
new CommandCondition(),
new ScriptCondition(),
new ContentPathCondition(),
Expand Down
17 changes: 17 additions & 0 deletions PowerShellProtect/Analyze/Conditions/LanguageMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Engine.Analyze.Conditions
{
/// <summary>
/// Matches the language mode of the PowerShell runspace that AMSI is scanning.
/// </summary>
internal class LanguageModeCondition : StringCondition
{
public override string Name => "languagemode";

public override string Description => "Matches the language mode of the PowerShell runspace that submitted the script.";

public override string GetValue(ScriptContext context)
{
return context.LanguageMode ?? string.Empty;
}
}
}
2 changes: 1 addition & 1 deletion PowerShellProtect/Cmdlets/NewConditionCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace PowerShellProtect.Cmdlets
public class NewConditionCommand : PSCmdlet
{
[Parameter(Mandatory = true)]
[ValidateSet("admin", "domaincontroller", "computername", "domain", "ApplicationName", "command", "script", "contentpath", "variable", "member", "string", "applicationHash", "assembly", "assemblyHash")]
[ValidateSet("admin", "domaincontroller", "computername", "domain", "languagemode", "ApplicationName", "command", "script", "contentpath", "variable", "member", "string", "applicationHash", "assembly", "assemblyHash")]
public string Property { get; set; }

[Parameter(Mandatory = true, ParameterSetName = "NotEquals")]
Expand Down
7 changes: 7 additions & 0 deletions PowerShellProtect/ScriptContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;

namespace Engine
{
Expand All @@ -11,11 +12,17 @@ public class ScriptContext
public string Script { get; set; }
public string ContentName { get; set; }
public string ApplicationName { get; set; }
/// <summary>
/// The language mode of the runspace that submitted the script to AMSI.
/// This is null when the script was not submitted from a PowerShell runspace.
/// </summary>
public string LanguageMode { get; set; }
public Guid Id { get; set; } = Guid.NewGuid();
public static ConcurrentDictionary<DateTime, ScriptContext> History { get; } = new ConcurrentDictionary<DateTime, ScriptContext>();

public ScriptContext()
{
LanguageMode = Runspace.DefaultRunspace?.SessionStateProxy?.LanguageMode.ToString();
History.TryAdd(DateTime.Now, this);
}

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ Install-Module PowerShellProtect
Install-PowerShellProtect
```

## Enforce Constrained Language Mode

Use a `languagemode` condition with a blocking action to prevent scripts from running unless the submitting PowerShell runspace is in Constrained Language Mode:

```powershell
$condition = New-PSPCondition -Property languagemode -NotEquals -Value ConstrainedLanguage
$action = New-PSPAction -Block
$rule = New-PSPRule -Name 'Require Constrained Language Mode' -Condition $condition -Action $action
$configuration = New-PSPConfiguration -Rule $rule -Action $action
```

This rule observes the mode of the runspace that AMSI is currently scanning. It is a guardrail, not a replacement for enforcing Constrained Language Mode with WDAC, AppLocker, or a locked-down session configuration: code running with Full Language capabilities can attempt to disable AMSI or change its own session state before a later scan.

## Resources

- [License](./LICENSE)
Expand Down