1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
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
|