SCREECHVR V18: PHOTON RPC + GAMEPLAY SECURITY Date: 2026-10-30 Scope: Read-only review of game-owned Unity networking scripts and official Photon PUN documentation. No code or production changes. Audit covered custom gameplay scripts; vendor SDK/demo code and every scene prefab were not exhaustively audited. PUN Cloud is relay/matchmaking, not an authoritative game server. SUMMARY Source confirms several RPCs accept state/actions without sender validation, and local clients determine movement, tag outcomes, and combat hits. PUN reliably relays client messages and enforces PhotonView ownership/serialization rules, but ownership proves who currently controls a view, not whether reported damage, position, or gameplay is legitimate. These are real trust-boundary limitations; actual abuse still depends on the attacker being in the room and on the script/ scene being active. No evidence that a remote player can arbitrarily invoke unrelated PhotonViews absent the proper view/RPC path; avoid claiming that. 1. RPC / EVENT / STATE INVENTORY Tag and Infection - `Assets/Scripts/Gamemode/TagPlayer.cs`: local `Update`/`TryTagWithHand` checks local hand overlap and calls `TagManager.AttemptTag`. `[PunRPC] RPC_Infect(int actorId)` checks actorId against the target view owner, then applies infected visuals/freeze/speed locally. This is a useful target check but not sender authorization. `[PunRPC] RPC_Cure()` and `RPC_SetTagger(bool)` have no `PhotonMessageInfo` sender/owner validation and mutate client state. - `Assets/Scripts/Gamemode/TagManager.cs`: `AttemptTag` is client logic, sends `RPC_Infect`, `RPC_Cure`, `RPC_SetTagger` to All. No MasterClient-only adjudication or trusted canonical state. Clients can call this logic locally; any participant can attempt repeated legal RPC invocations on an accessible view. RPCs are unbuffered, but unbuffered does not prevent live spam/replay. `OnLastPlayerInfected(int actorId)` can increment win state without validating sender/outcome; `CheckWinCondition` is unfinished; `UpdateFreezeTimers` empty. - Confirmed: game mode outcomes are client-authoritative and RPC handlers lack sender checks. Do not claim “self-tag always works”: `RPC_Infect` checks the supplied target actor ID, but sender authorization and full call context are missing. A determined room client may still invoke state changes; exact exploit path requires runtime test. Player state / movement - `Assets/Scripts/PhotonVRPlayer.cs`: `OnPhotonSerializeView` replicates owner pose/body data; view ownership selects writer but receiver does no movement plausibility validation. `[PunRPC] MetaRPC()` takes no sender argument/check and applies metadata. Player colour/cosmetics/username are client-controlled presentation/custom properties, not proof of entitlement or identity. - `Assets/Scripts/PhotonVRManager.cs`: `SetColour`, `SetCosmetics`, `SetUsername` set local player properties. Join/create filters use client-set room `queue` and `version`; these properties are matchmaking labels, not authorization. No custom `OnPlayerPropertiesUpdate` / `OnRoomPropertiesUpdate` validator or `OnMasterClientSwitched` implementation found in scanned main scripts. - `Assets/Scripts/AIWanderFollowV2.cs` / `NetworkAi.cs`: AI serialization sends position/follow state from PhotonView owner; this makes that client the AI simulation authority. `AIWanderFollowV2.Update()` permits logic while in-room on all clients, while NetworkAi gates NavMeshAgent by owner. Potential divergent local decision state; not proof of a remote exploit without scene/runtime test. - `Assets/Scripts/GrabbableObj.cs`: `OnPhotonSerializeView` sends rigidbody position/rotation/velocity. Grab requests ownership; release transfers to MasterClient. `Network_State(bool)` sends buffered `Grab_State(bool)`, which changes state without sender validation. Buffered state persists/replays to late joiners for that view; verify cleanup/lifetime. Ownership transfer enables the interaction, not validation of proximity/physics. Welding / physics toys - `Assets/Scripts/WeldGun.cs`: previously audited selection code transfers ownership before weld. Welding and the resulting physics are client-driven; no trusted receiver adjudicates distance/eligibility. Existing null cleanup fix does not make welds authoritative. Preserve legitimate ownership transfer and weld-line rendering. - `Assets/Scripts/ThusterController.cs` / `ThusterBoi.cs`: input is locally owner-gated, but controller sends `ToggleRPC` buffered to all every frame while active/inactive. `SyncStateRPC(bool)` has no sender/ownership check. Confirmed RPC amplification and persistent buffer growth; state can be set by an RPC sender. Rate/state transitions should replace per-frame buffered toggles. - `Assets/Scripts/Flash_Light.cs`: held input sends toggle RPC; handler has no sender check. A toggle is non-idempotent: duplicates invert state. Existing sandbox audit identified held-button flood behavior. - `Assets/Scripts/SandboxItems.cs`, `OwnItemGate.cs`: object gating/ownership patterns exist; ownership is not a server validation of interactions. PVP / damage / kick - `Assets/Scripts/PVP_Gun.cs`: owner-side firing sends parameterless `RPC_Shoot` to All; the owner then raycasts locally and calls target `PVP_Player.Damage_(damage, photonView.ViewID)`. Hit target, ray result, damage and headshot multiplier are determined client-side. `RPC_StartReload()` and `RPC_ShowHitMarker(int damage)` lack sender validation; hit marker amount is client-supplied. No receiver-side fire-rate/sequence validation found. - `Assets/Scripts/PVP_Player.cs`: health/damage/death follows the client combat call path; attacker ViewID is not proof of a valid hit. `PVP_ROCKET.cs`, `BulletBehavior.cs`, `BulletTrailSync.cs`, `HitBox.cs`, `BulletManager.cs`, and `Object_Type.cs` support client-driven projectile/collision/raycast behavior; no authoritative hit validation found in scanned paths. - `Assets/Scripts/DamagePlayer.cs`: client-driven target/explosion damage; earlier sandbox audit observed damage multiplied by player count. Keep this finding scoped to that script/path, not all damage mechanics. - `Assets/Scripts/Spokes_KickGun.cs`: `TriggerKickServer()` and `DeactivateKickServer()` are buffered RPCs without sender checks; they toggle collider/visual state. Trigger overlap is not authoritative damage/kick proof. - `Assets/Scripts/Kick_Player.cs`: local trigger exits app (`Application.Quit`) on kick-layer collision; it is not a server-enforced kick/ban. Events, properties, master client - Search of the main game-owned Scripts tree found no custom `PhotonNetwork.RaiseEvent` calls. Photon PUN events/serialization still exist at framework level. - No custom master-client callback or centralized MasterClient state authority was found in scanned scripts. Thus there is no source-confirmed custom master exploit path in this audit. In PUN, any room actor can become MasterClient through normal migration; MasterClient is still a player client, not trusted server compute. Do not place anti-cheat authority there. - Player/room properties are server-relayed, but values are set by clients. They are suitable for presentation/matchmaking only unless validated externally. - RPC spam/replay findings: buffered thruster toggle every frame; buffered grab state; buffered kick toggles; unbuffered tag/combat/flashlight calls can still be repeated live. No common application-level RPC rate limit or action nonce layer was found. 2. WHAT IS CONFIRMED VS THEORETICAL Confirmed in source: - Owner-reported movement and physics state are replicated without server-side plausibility checks. - Client calculates PVP hit/damage and invokes target damage path. - Tag state changes use client-invoked RPCs; several handlers lack sender checks. - Thuster RPC spam/buffering and toggle-style duplicate sensitivity. - `PhotonView.IsMine` / ownership gates choose a client controller; they do not prove the reported action is fair or physically possible. - No main-script custom RaiseEvent authority layer or MasterClient security authority was found. Not established by source alone: - Whether a specific arbitrary RPC call succeeds from an attacker-controlled PhotonView/actor at runtime; target PhotonView, room membership, RPC list, version, and scene lifecycle matter. - Live prefab ownership options, Photon dashboard settings, room property policies, MasterClient switching, active script instances, and exploit rates. - That a modified client can force arbitrary damage in every mode. It can control its own outgoing data; recipients currently lack authoritative hit adjudication in scanned paths, but exact path must be reproduced in a test room. 3. DEFENSE THAT FITS PUN CLOUD PUN Cloud cannot securely validate gameplay outcomes because it relays client messages rather than running game simulation. Realistic protections without a dedicated server: - Add `PhotonMessageInfo info` to sensitive RPCs and validate `info.Sender` actor against expected owner/controller and legal game state. This blocks accidental or cross-actor calls, not a modified legitimate owner lying about its own play. - For state-setting RPCs, send desired state (set active true/false) rather than toggle; make handlers idempotent. Reject invalid transitions, stale sequence numbers, wrong target view/actor, and impossible parameter ranges. - Rate-limit per actor/action; avoid buffered high-frequency/transient events. Buffer only durable state required by late joiners, and clear cached events on despawn/reset. Keep VFX/audio separate from gameplay state. - Tag/Infection: derive tag requests from a local hand overlap, send requester, target, timestamp/sequence; receivers check sender owns the tagger view, target exists, not self/frozen/already infected, cooldown and mode. For fairness with no trusted server, make MasterClient a consistency coordinator only, replicate canonical round state with room properties/sequence and re-elect/reconcile on master switch; state explicitly that this is not cheat-proof because master is a client. Avoid granting wins from an unverified RPC. - Movement: keep owner-simulated gorilla locomotion for responsiveness. Add conservative remote plausibility flags/telemetry and reconciliation only for obvious malformed/extreme values; client-supplied positions cannot prove speed hacks and should not auto-ban. - PVP: validate sender owns weapon, ammo/cooldown/weapon state and target bounds; move damage to an authoritative service if competitive integrity is required. Client-side raycast remains best-effort, not secure validation. Avoid claiming MasterClient raycast as secure authority. - Welding: preserve ownership transfer and freeform construction. Validate object/view exists, ownership at action, proximity/eligibility and rate; use deterministic/idempotent weld-state IDs. With no authoritative physics server, accept that clients can manipulate their owned objects; isolate effects from persistent economy/competitive outcomes. - MasterClient: treat as orchestration/host migration. Persist essential room state in custom room properties with expected-value/sequence checks and have clients reconcile on `OnMasterClientSwitched`. Do not trust a MasterClient claim for bans, currency, wins, or irreversible progression. - For strong validation of position, collision, damage, and winners, add an authoritative game server or migrate the relevant mode to server-authoritative networking. This is a larger architectural choice. 4. REGRESSION TESTS Staging/test room only: 1. Two legitimate clients: normal Tag and Infection tagging, cure/freeze, late join, disconnect/rejoin, round reset, winner/leaderboard consistency. 2. Invoke each tag RPC from wrong actor/view, wrong target, self, duplicate, stale sequence, and cooldown; reject invalid transitions without blocking normal hand-contact tag. 3. Flood flashlight/thruster/kick toggles and replay buffered state; verify bounded RPC/buffer growth and stable state for existing and late-joining clients. 4. Verify only valid owner can control each weapon/object view; test ownership request, transfer, owner leave, MasterClient migration, and cleanup. 5. PVP: normal fire/reload/headshot/rocket works; inject impossible damage, target, hit marker, rapid fire, and duplicate RPCs; ensure any server-side authoritative mode rejects or flags these. In PUN-only mode document residual client authority rather than expecting perfect prevention. 6. Movement: test normal VR locomotion, tracking loss, lag, teleport-like spikes, packet jitter; avoid false positives. Confirm no auto-ban based on telemetry. 7. Welding: two clients grab/transfer/weld items, one item cleanup, owner leaves; legitimate weld line/state remains consistent and stale references are pruned. 8. Sandbox interactions, all active modes, voice/room joins, and scene transitions remain intact after any future implementation. OFFICIAL PHOTON DOCUMENTATION - PUN RPCs and RaiseEvent: https://doc.photonengine.com/pun/current/gameplay/rpcsandraiseevent - PhotonView Ownership & Control: https://doc.photonengine.com/pun/current/gameplay/ownershipandcontrol - PUN synchronization and state / `OnPhotonSerializeView`: https://doc.photonengine.com/pun/current/gameplay/synchronization-and-state - Master Client and Host Migration: https://doc.photonengine.com/pun/current/gameplay/hostmigration No implementation. No code or production changes. END OF REPORT