How gengine's three-tier safety model, tool annotations, input validation, and rate limiting protect your project from destructive AI operations.
Safety System
gengine includes a multi-layer safety system that sits between every AI tool call and the Unreal Editor. It validates parameters, enforces safety tiers, blocks dangerous console commands, sanitizes output, and rate-limits requests.
Three Safety Tiers
Every gengine installation operates in one of three safety modes, configured in Project Settings > Plugins > gengine.
| Tier | Who it's for | What it allows |
|---|---|---|
| ReadOnly | Reviewers, automated pipelines | Read-only operations only. All write, delete, and modify operations are blocked at the routing layer. |
| Supervised | Default for most users | All operations allowed, but destructive operations (delete, delete_actors) require an explicit confirm: true parameter. |
| Unrestricted | Advanced users, CI/CD | All operations allowed without additional confirmation. Destructive operations execute immediately. |
The default tier is Supervised.
Warning: Setting the tier to Unrestricted removes the confirmation interlock on destructive operations. Only use this in isolated environments where accidental deletion is acceptable.
Tool Annotations
Each operation in gengine's tool registry carries MCP annotations that communicate its safety profile to AI clients. These annotations affect how AI models schedule operations and how the safety layer handles them.
| Annotation | Meaning | Operations |
|---|---|---|
readOnlyHint | Does not modify editor state | get_level_actors, search, get_info, get_output_log, get_material_info |
destructiveHint | May permanently delete or overwrite data | delete_actors, delete (assets) |
idempotentHint | Calling twice produces same result as once | set_property, move_actor, set_material_parameters |
openWorldHint | May interact with external systems or network state | run_console_command, open_level |
In ReadOnly tier, any operation not tagged readOnlyHint is rejected before it reaches the Unreal game thread.
Server-Side Enforcement
Safety is enforced in C++ on the game thread, not in the MCP bridge. This means safety rules apply even when the bridge is bypassed via direct REST API calls.
// From MCPAutoRouter.cpp — tier enforcement
EMCPSafetyTier RequiredTier = Tool->GetSafetyRequirement();
if (RequiredTier > CurrentTier)
{
return FMCPResult::Error(FString::Printf(
TEXT("Operation '%s' requires safety tier '%s'. Current tier: '%s'."),
*OperationName,
*SafetyTierToString(RequiredTier),
*SafetyTierToString(CurrentTier)
));
}
The confirm: true interlock for destructive operations is also enforced server-side:
// From MCPParamValidator.cpp — destructive confirmation check
if (Tool->HasAnnotation(EMCPAnnotation::Destructive))
{
bool bConfirmed = false;
if (!Params.TryGetBoolField(TEXT("confirm"), bConfirmed) || !bConfirmed)
{
return FMCPValidationResult::Error(
TEXT("Destructive operation requires 'confirm: true' parameter.")
);
}
}
Input Validation
All parameters pass through MCPParamValidator before reaching tool handlers. The validator checks:
Type checking
Every parameter is validated against its declared JSON Schema type. Passing a string where a float is expected returns a type error before any editor state is touched.
String sanitization
String parameters are checked for characters and patterns that could cause injection or path traversal:
Blocked characters in name fields: .., /, \, <, >, :, ", |, ?, *, null bytes
Blocked path traversal patterns:
../and..\— parent directory traversal- Absolute paths starting with
/in name fields (content paths use a separate validated field) - UNC paths (
\\server\share) - Drive-letter paths (
C:\) in name fields
// From MCPParamValidator.cpp
static bool ContainsPathTraversal(const FString& Value)
{
return Value.Contains(TEXT("../"))
|| Value.Contains(TEXT("..\\"))
|| Value.Contains(TEXT("\0"))
|| FRegexMatcher(FRegexPattern(TEXT("^[A-Za-z]:\\\\")), Value).FindNext();
}
Numeric validation
All numeric parameters are checked for finite values. NaN and infinity are rejected:
static bool IsFiniteFloat(float Value)
{
return FMath::IsFinite(Value) && !FMath::IsNaN(Value);
}
Location, rotation, and scale components each must pass this check before the actor transform operation proceeds.
Array bounds
Array parameters (e.g., names in delete_actors) are capped at declared maximums. delete_actors caps at 200 entries to prevent accidental mass deletion.
Console Command Blocklist
run_console_command validates the command string against a blocklist of prefix patterns before executing anything. Blocked prefixes:
| Blocked Prefix | Reason |
|---|---|
quit, exit | Would close the editor |
RestartEditor | Editor restart |
open | Level loading — use open_level instead |
disconnect, reconnect | Network state changes |
exec | Executes arbitrary script files |
obj savepackage | Raw package write bypass |
servertravel, clienttravel | Travel commands |
killall | Mass actor destruction bypass |
DestroyAll | Same — bypasses delete confirmation |
suicide | Player-state side effects |
slomo | Global time scale — affects recording/capture |
Commands not on the blocklist are forwarded to the editor console unchanged.
Note: The blocklist is a prefix match, not an exact match.
quit gameis blocked because it starts withquit. This prevents bypass via suffixes.
Output Sanitization
MCPOutputSanitizer processes all tool responses before they are sent back through the MCP bridge. It strips:
- Absolute file system paths (replaced with content-relative paths)
- Internal engine memory addresses
- Patterns that could be used for prompt injection:
<script>tags- Markdown link syntax pointing to
javascript:URIs - Embedded null bytes in string fields
- Control characters outside of standard whitespace
// From MCPOutputSanitizer.cpp — injection pattern stripping
static const TArray<FString> BlockedPatterns = {
TEXT("<script"),
TEXT("javascript:"),
TEXT("data:text/html"),
TEXT("vbscript:"),
};
FString FSanitizer::SanitizeString(const FString& Input)
{
FString Result = Input;
for (const FString& Pattern : BlockedPatterns)
{
Result = Result.Replace(*Pattern, TEXT("[removed]"), ESearchCase::IgnoreCase);
}
return Result;
}
Rate Limiting
gengine enforces per-session rate limits to prevent runaway AI agents from hammering the editor task queue.
Default limits
| Tier | Requests / minute | Burst allowance |
|---|---|---|
| Free | 60 | 10 |
| Pro | 600 | 50 |
| Enterprise | Unlimited | Unlimited |
Implementation
Rate limiting uses a token bucket algorithm implemented in C++:
// From MCPRateLimiter.cpp
bool FMCPRateLimiter::TryConsume(const FString& SessionId)
{
FBucket& Bucket = Buckets.FindOrAdd(SessionId);
const double Now = FPlatformTime::Seconds();
const double Elapsed = Now - Bucket.LastRefillTime;
// Refill tokens based on elapsed time
Bucket.Tokens = FMath::Min(
Bucket.MaxTokens,
Bucket.Tokens + Elapsed * Bucket.RefillRate
);
Bucket.LastRefillTime = Now;
if (Bucket.Tokens >= 1.0)
{
Bucket.Tokens -= 1.0;
return true;
}
return false; // Rate limit exceeded
}
When a request is rate-limited, the response is:
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Retry after 1 second.",
"retry_after_ms": 1000
}
Best Practices
Use the right tier for the context
- CI/CD pipelines that only read asset data: use ReadOnly tier.
- Interactive sessions with a developer at the keyboard: use Supervised tier (default).
- Automated batch operations with a tested, reviewed script: Unrestricted is acceptable if the script has been audited.
Always check referencers before deleting
Even in Unrestricted tier, call unreal_assets / referencers before delete. gengine will execute the delete — it won't protect you from broken references.
Treat run_console_command as an escape hatch
Use domain tools for structured operations. Reserve run_console_command for one-off diagnostic commands (stat fps, r.SetRes) that don't have a dedicated tool. Never pipe untrusted string data into a console command.
Review Activity tab after complex sessions
The Activity tab logs every tool call with parameters and results. After a multi-step AI session, review it to confirm operations executed as intended — especially before committing content to source control.