text Untitled 7 views
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