Security and Anti-Cheat

Server-authoritative validation, movement checks, teleport detection, hit validation, rate limiting, and transport encryption in Aether.

Aether uses server-authoritative validation to prevent cheating in competitive and social VR worlds. The aether-security crate provides movement validation, teleport detection, hit registration verification, action rate limiting, interest management, and transport hardening -- all as pure functions and lightweight structs with no external I/O.

Key Concepts

  • Server authority -- The server is the single source of truth for game state. Client inputs are validated before being applied.
  • Movement validation -- Per-tick velocity and acceleration checks against physics limits.
  • Teleport detection -- Detects impossible position jumps between server ticks.
  • Hit validation -- Server-side verification of hit claims (range, line-of-sight, timing).
  • Action rate limiting -- Token-bucket rate limiter with per-action cooldowns.
  • Interest management -- Controls which entities are visible to which players.
  • Transport security -- QUIC/TLS encryption and DDoS defense signals.

Architecture

All validators are composable and run in sequence on the server for every client input packet:

Client Input Packet
       |
       v
  Server Input Router
       |
  +----+--------+----------+----------+
  |    |        |          |          |
  v    v        v          v          v
 Move  Teleport  Hit    Rate Limit  WASM
 Valid Detect   Valid   Check       Sandbox
  |    |        |          |          |
  +----+--------+----------+----------+
       |
       v
   Accept / Reject (CheatVerdict)
       |
  [if accepted]
       v
  Apply to Server State
       |
       v
  Interest Manager -> Filter Entity Updates -> Send to Client

Each validator returns a typed result (accept or reject with reason), making the pipeline testable and auditable.

Movement Validation

The movement validator checks that player position changes respect physics constraints:

use aether_security::{MovementValidator, MovementConfig, ValidationResult};

let config = MovementConfig {
    max_speed: 20.0,          // meters per second
    max_acceleration: 50.0,   // meters per second squared
    tolerance: 1.1,           // 10% tolerance for network jitter
};

let result = MovementValidator::validate(
    prev_position,  // [f32; 3]
    new_position,   // [f32; 3]
    delta_time,     // seconds since last tick
    &config,
);

match result {
    ValidationResult::Accepted => {
        // Apply the new position to server state
    }
    ValidationResult::Rejected { reason } => {
        // Log the cheat signal, use server-predicted position instead
    }
}

The validator computes:

  • Velocity = distance / delta_time. Rejected if > max_speed * tolerance.
  • Acceleration = velocity delta / delta_time. Rejected if > max_acceleration * tolerance.

Teleport Detection

The teleport detector is stateful, tracking each entity's last known position:

use aether_security::{TeleportDetector, TeleportResult};

let mut detector = TeleportDetector::new(100.0); // max 100m displacement per tick

// First check establishes baseline
detector.check(entity_id, position_a, timestamp_a);

// Subsequent checks flag impossible jumps
match detector.check(entity_id, position_b, timestamp_b) {
    TeleportResult::Normal => {
        // Movement is within physically possible bounds
    }
    TeleportResult::Teleported { distance } => {
        // Impossible displacement detected
        // distance: actual displacement between ticks
    }
}

The detector flags movements where the distance between consecutive positions exceeds the physically possible displacement given the time elapsed and maximum speed.

Hit Validation

The hit validator verifies client-submitted hit claims against server-authoritative state:

use aether_security::{HitValidator, HitClaim, HitResult};

let claim = HitClaim {
    attacker_id: attacker,
    target_id: target,
    weapon_range: 10.0,
    timestamp: hit_time,
    attacker_position: [1.0, 0.0, 0.0],
    target_position: [5.0, 0.0, 0.0],
};

match HitValidator::validate(&claim, &server_state) {
    HitResult::Valid => {
        // Apply damage to target
    }
    HitResult::Invalid { reason } => {
        // Reject the hit claim
    }
}

Validation checks:

CheckDescription
RangeDistance between attacker and target must be within weapon range
Line of sightPlausibility check that the target was visible to the attacker
Timing windowHit timestamp must be within an acceptable window of the server tick
Target aliveTarget must be in a damageable state on the server

Action Rate Limiting

The action rate limiter uses a token-bucket algorithm per (player, action) pair:

use aether_security::{ActionRateLimiter, RateLimitResult};

let mut limiter = ActionRateLimiter::new();

// Configure per action type
limiter.configure_action("attack", 2.0, 10); // 2 tokens/sec, burst of 10
limiter.configure_action("jump", 1.0, 3);    // 1 token/sec, burst of 3

match limiter.try_consume(player_id, "attack", now) {
    RateLimitResult::Allowed { remaining } => {
        // Action permitted
    }
    RateLimitResult::Limited { retry_after } => {
        // Action rejected, try again after `retry_after` duration
    }
}

Each action type has independent token generation rates and burst capacities. This prevents action flooding (spamming attacks, jumps, or interactions faster than the game design allows).

Interest Management

Interest management controls which entities are visible to which players, preventing information leaks:

use aether_security::{InterestManager, InterestZone};

let mut manager = InterestManager::new(200.0); // 200m default interest radius

// Add spatial interest zones with access control
manager.add_zone(InterestZone {
    center: [100.0, 0.0, 100.0],
    radius: 50.0,
    allowed_players: vec![player_a, player_b],
});

// Query visible entities for a player
let visible = manager.visible_entities(
    player_id,
    player_position,
    &all_entities,
);
// Only entities within interest radius and with appropriate access are returned

Culling rules:

  • Entities outside the player's interest radius are not sent to the client.
  • Hidden entities (invisible players, GM-cloaked entities) are filtered unless the viewer has explicit permission.
  • Interest zones can restrict visibility to specific player lists.

WASM Sandbox Security

Scripts running in the WASM sandbox are constrained by capability limits (WasmSandboxCapability). Scripts requesting unauthorized capabilities are rejected, and module tampering (hash mismatch) is detected via WasmSurfaceError::ModuleTampered.

Transport Security

The gateway enforces encrypted QUIC/TLS transport for all connections via TransportPolicy. DDoS defense uses a state machine (Normal -> Alert -> Mitigation -> Recovery) triggered by AttackSignal variants like volumetric floods.

Default Constants

All default limits are defined as constants and overridable via environment variables:

ConstantDefaultDescription
DEFAULT_MAX_SPEED20.0 m/sMaximum player movement speed
DEFAULT_MAX_ACCELERATION50.0 m/s^2Maximum acceleration
DEFAULT_SPEED_TOLERANCE1.110% tolerance for network jitter
DEFAULT_MAX_TELEPORT_DISTANCE100.0 mMax displacement per tick
DEFAULT_INTEREST_RADIUS200.0 mDefault entity visibility radius
DEFAULT_MAX_HIT_RANGE100.0 mMaximum weapon hit range
DEFAULT_HIT_TIMING_WINDOW_MS500Acceptable hit timing window
DEFAULT_RATE_LIMIT_BURST10Default token bucket burst

Cheat Verdicts

When a validator rejects an input, it produces a CheatVerdict with structured information:

use aether_security::{CheatVerdict, CheatSignal};

// Verdicts carry the signal type and severity
let verdict = CheatVerdict {
    player_id: cheater,
    signal: CheatSignal::SpeedHack { velocity: 45.0, max_allowed: 22.0 },
    severity: 0.8,
    timestamp: now,
};

Verdicts are collected and forwarded to the trust and safety system for action (warnings, kicks, or bans) based on accumulated severity scores.