feat!: add cooperative execution-time limits across engine, VM, and binding (#539)

- Introduce ExecutionTimer/ExecutionTimerConfig to allow limiting evaluating time.
- To amortize time checking costs, checking interval can be configured via the notion of work units
- A global fallback time limit can be set to universally limit all evaluation in addition to engine level limit setting.
- Implement limnits in interpreter and RVM. In RVM, also handle suspend/resume so that time during pause is not counted.
- Add engine-level APIs to set/clear per-engine timer configuration and apply global fallback defaults.
- Surface execution-time limits through FFI and C# bindings
- Add C# tests and example usage to validate engine overrides, global fallback behavior, and compiled policy enforcement.
- Expand docs for execution-time limit
- Add interpreter YAML cases and VM unit tests for time-limit behavior and deterministic time sources.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2026-01-28 05:58:03 +05:30
committed by GitHub
parent e68e852ee3
commit 394625d4bc
32 changed files with 2259 additions and 183 deletions

View File

@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Linq;
using System.Text.Json;
namespace TargetExampleApp;
@@ -32,14 +33,14 @@ allow if {
# Allow network security groups with proper inbound rules
allow if {
input.type == ""Microsoft.Network/networkSecurityGroups""
count([rule |
rule := input.properties.securityRules[_]
rule.properties.direction == ""Inbound""
rule.properties.access == ""Allow""
rule.properties.sourceAddressPrefix == ""*""
rule.properties.destinationPortRange in [parameters.allowedPorts]
]) == 0
input.type == ""Microsoft.Network/networkSecurityGroups""
count([rule |
rule := input.properties.securityRules[_]
rule.properties.direction == ""Inbound""
rule.properties.access == ""Allow""
rule.properties.sourceAddressPrefix == ""*""
rule.properties.destinationPortRange in [parameters.allowedPorts]
]) == 0
}";
private const string AZURE_STORAGE_POLICY_ASSIGNMENT = @"
@@ -50,44 +51,58 @@ import rego.v1
parameters.requiredTLSVersion = ""TLS1_2""
parameters.allowedPorts = [""22"", ""3389""]";
private const string EXECUTION_TIMER_POLICY = @"
package limits.timer
import rego.v1
triplet_count := count([1 |
x := data.values[_]
y := data.values[_]
z := data.values[_]
])
";
private const string EXECUTION_TIMER_QUERY = "data.limits.timer.triplet_count";
private const int EXECUTION_TIMER_VALUE_COUNT = 40;
// Test data constants
private const string COMPLIANT_STORAGE_ACCOUNT = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""compliantstorageacct"",
""location"": ""eastus"",
""kind"": ""StorageV2"",
""properties"": {
""supportsHttpsTrafficOnly"": true,
""minimumTlsVersion"": ""TLS1_2"",
""allowBlobPublicAccess"": false,
""encryption"": {
""services"": {
""blob"": { ""enabled"": true },
""file"": { ""enabled"": true }
}
}
},
""tags"": {
""environment"": ""production""
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""compliantstorageacct"",
""location"": ""eastus"",
""kind"": ""StorageV2"",
""properties"": {
""supportsHttpsTrafficOnly"": true,
""minimumTlsVersion"": ""TLS1_2"",
""allowBlobPublicAccess"": false,
""encryption"": {
""services"": {
""blob"": { ""enabled"": true },
""file"": { ""enabled"": true }
}
}
},
""tags"": {
""environment"": ""production""
}
}";
private const string NON_COMPLIANT_STORAGE_ACCOUNT = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""insecurestorageacct"",
""location"": ""westus"",
""kind"": ""Storage"",
""properties"": {
""supportsHttpsTrafficOnly"": false,
""minimumTlsVersion"": ""TLS1_0"",
""allowBlobPublicAccess"": true,
""encryption"": {
""services"": {
""blob"": { ""enabled"": false },
""file"": { ""enabled"": false }
}
}
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""insecurestorageacct"",
""location"": ""westus"",
""kind"": ""Storage"",
""properties"": {
""supportsHttpsTrafficOnly"": false,
""minimumTlsVersion"": ""TLS1_0"",
""allowBlobPublicAccess"": true,
""encryption"": {
""services"": {
""blob"": { ""enabled"": false },
""file"": { ""enabled"": false }
}
}
}
}";
static void Main(string[] args)
@@ -96,7 +111,6 @@ parameters.allowedPorts = [""22"", ""3389""]";
try
{
DemonstrateMemoryLimitHelpers();
DemonstrateTargetFunctionality();
Console.WriteLine("\n=== Target demonstration completed successfully! ===");
}
@@ -157,6 +171,9 @@ parameters.allowedPorts = [""22"", ""3389""]";
// 4. Demonstrate thread-safe concurrent evaluation
Console.WriteLine("\n4. Testing concurrent evaluation from multiple threads:");
DemonstrateConcurrentEvaluation(compiledPolicy);
Console.WriteLine("\n5. Execution timer configuration:");
DemonstrateExecutionTimer();
}
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
@@ -214,45 +231,6 @@ parameters.allowedPorts = [""22"", ""3389""]";
Console.WriteLine("✓ No locks required - CompiledPolicy is thread-safe!");
}
static void DemonstrateMemoryLimitHelpers()
{
Console.WriteLine("REGORUS MEMORY LIMIT UTILITIES");
Console.WriteLine("==============================");
var originalLimit = Regorus.MemoryLimits.GetGlobalMemoryLimit();
var originalThreshold = Regorus.MemoryLimits.GetThreadMemoryFlushThreshold();
try
{
const ulong demoLimit = 64 * 1024 * 1024;
Regorus.MemoryLimits.SetGlobalMemoryLimit(demoLimit);
Console.WriteLine($"✓ Global memory limit set to {Regorus.MemoryLimits.GetGlobalMemoryLimit():N0} bytes");
const ulong flushThreshold = 256 * 1024;
Regorus.MemoryLimits.SetThreadFlushThresholdOverride(flushThreshold);
Console.WriteLine($"✓ Thread flush threshold configured for {Regorus.MemoryLimits.GetThreadMemoryFlushThreshold():N0} bytes");
Console.WriteLine("Attempting an allocation that exceeds a 1 byte budget...");
Regorus.MemoryLimits.SetGlobalMemoryLimit(1);
using var engine = new Regorus.Engine();
var payload = new string('x', 128 * 1024);
engine.SetInputJson($"{{\"payload\":\"{payload}\"}}");
}
catch (InvalidOperationException error)
{
Console.WriteLine($"✗ Memory limit exceeded: {error.Message}");
}
finally
{
Regorus.MemoryLimits.SetThreadFlushThresholdOverride(null);
Regorus.MemoryLimits.SetGlobalMemoryLimit(originalLimit);
Regorus.MemoryLimits.FlushThreadMemoryCounters();
}
Console.WriteLine();
}
static void DemonstratePolicyInfo(Regorus.CompiledPolicy compiledPolicy)
{
Console.WriteLine("Getting policy metadata using GetPolicyInfo()...");
@@ -328,4 +306,57 @@ parameters.allowedPorts = [""22"", ""3389""]";
Console.WriteLine($"✗ Failed to get policy info: {ex.Message}");
}
}
static void DemonstrateExecutionTimer()
{
var dataJson = JsonSerializer.Serialize(new
{
values = Enumerable.Range(0, EXECUTION_TIMER_VALUE_COUNT).ToArray()
});
var fallback = new Regorus.ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
var relaxed = new Regorus.ExecutionTimerConfig(TimeSpan.FromMilliseconds(1000), checkInterval: 1);
Console.WriteLine($" Configuring fallback timer (limit={fallback.Limit.TotalMilliseconds:F0} ms, interval={fallback.CheckInterval})...");
Regorus.Engine.SetFallbackExecutionTimerConfig(fallback);
try
{
using var engine = new Regorus.Engine();
engine.AddPolicy("limits_timer.rego", EXECUTION_TIMER_POLICY);
engine.AddDataJson(dataJson);
Console.WriteLine(" Evaluating under fallback limit (expected failure)...");
try
{
engine.EvalRule(EXECUTION_TIMER_QUERY);
Console.WriteLine(" ⚠ Evaluation unexpectedly succeeded under fallback limit.");
}
catch (Exception ex)
{
Console.WriteLine($" ✓ Fallback enforced: {ex.Message}");
}
Console.WriteLine($" Applying per-engine override ({relaxed.Limit.TotalMilliseconds:F0} ms) and retrying...");
engine.SetExecutionTimerConfig(relaxed);
var result = engine.EvalRule(EXECUTION_TIMER_QUERY);
Console.WriteLine($" ✓ Override succeeded; triplet_count = {result}");
Console.WriteLine(" Clearing engine override to restore fallback...");
engine.ClearExecutionTimerConfig();
try
{
engine.EvalRule(EXECUTION_TIMER_QUERY);
Console.WriteLine(" ⚠ Evaluation unexpectedly succeeded after clearing override.");
}
catch (Exception ex)
{
Console.WriteLine($" ✓ Fallback restored: {ex.Message}");
}
}
finally
{
Regorus.Engine.ClearFallbackExecutionTimerConfig();
}
}
}