Intermediate

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.

TierWho it's forWhat it allows
ReadOnlyReviewers, automated pipelinesRead-only operations only. All write, delete, and modify operations are blocked at the routing layer.
SupervisedDefault for most usersAll operations allowed, but destructive operations (delete, delete_actors) require an explicit confirm: true parameter.
UnrestrictedAdvanced users, CI/CDAll 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.

AnnotationMeaningOperations
readOnlyHintDoes not modify editor stateget_level_actors, search, get_info, get_output_log, get_material_info
destructiveHintMay permanently delete or overwrite datadelete_actors, delete (assets)
idempotentHintCalling twice produces same result as onceset_property, move_actor, set_material_parameters
openWorldHintMay interact with external systems or network staterun_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 PrefixReason
quit, exitWould close the editor
RestartEditorEditor restart
open Level loading — use open_level instead
disconnect, reconnectNetwork state changes
exec Executes arbitrary script files
obj savepackageRaw package write bypass
servertravel, clienttravelTravel commands
killallMass actor destruction bypass
DestroyAllSame — bypasses delete confirmation
suicidePlayer-state side effects
slomoGlobal 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 game is blocked because it starts with quit. 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

TierRequests / minuteBurst allowance
Free6010
Pro60050
EnterpriseUnlimitedUnlimited

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.