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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
|
SCREECHVR V16: MODERATOR + CURRENCY SECURITY REVIEW
Date: 2026-10-30
Scope: Read-only review of supplied source bundle, local Unity source, V08 draft,
and official documentation. No code, revisions, dashboards, or production
services changed. V08 is explicitly a draft, not verified as deployed.
EXECUTIVE SUMMARY
The game derives moderator state from client-writable PlayFab UserData and then
uses that local state to gate moderator UI/actions. This is not a trusted
authorization boundary. In V08, ReportPlayer checks client-supplied ReporterId
against a hardcoded list and then invokes a privileged ban operation; if the
handler is callable by clients, this is critical authorization bypass. The
draft ServerGrantCurrency has no role check and grants to the authenticated
caller. Unity also has multiple direct client API currency grant paths. Their
actual exploitability depends partly on PlayFab API/title settings and live
backend configuration; source confirms the requests and trust assumptions, not
the production settings.
1. MODERATOR STATUS AND ACTIONS
Unity source reviewed:
- `! Screech VR/Screech VR/Assets/Scripts/Oculus_Login.cs`
- `CreateKeyIfNot()` reads UserData key `Moderator`; if absent it writes
`FALSE` using client `UpdateUserData`. A stored `TRUE` sets local
`AccountPref = Moderator`.
- No client source path found that legitimately assigns `TRUE`; this does not
make the key secure. A player with client write access can attempt to set
their own key.
- `OnLoginSuccess()` also starts account-data reads; no server-verified role
authorization is shown in this login path.
- `! Screech VR/Screech VR/Assets/Scripts/Check_Mod.cs`
- `CheckModeratorCoroutine` reads local `Oculus_Login.Instance.AccountPref`
and enables moderator-only objects. This is client-side UI/object gating.
- `! Screech VR/Screech VR/Assets/Scripts/Mod_Toggle.cs`
- Toggle checks collider/finger and, in the Yes path, local Photon queue
`MODDED`. It does not validate trusted moderator status.
- Related callers: `SmilerTouch.cs` and `TouchKick.cs` use local moderator checks;
their Photon event 230 path asks the target client to quit. This is not a
server-enforced kick/ban. `GDH Packages/LeaderBoard/Scripts/LeaderBoardManager.cs`
invokes CloudScript `ReportPlayer` (see below).
CloudScript reviewed:
- `revisions/SCREECHVR_PLAYFAB_REVISION_V08_2026-10-29.js` (header says draft,
not deployed). `isModServerSide` reads `IsMod` from player ReadOnlyData
(lines 56-66); `VerifyModerator` uses `currentPlayerId` (508-513). These are
safer patterns in the draft but do not protect every handler.
- V08 `ReportPlayer` (382-410) instead checks client `Args.ReporterId` against
hardcoded moderator IDs (392-395), then uses client `Args.TargetId` for ban
(and client-supplied report details). It does not call `isModServerSide` or
bind reporter identity to `currentPlayerId`. This is a confirmed flaw in the
draft source. Severity: Critical if client-callable/deployed; deployment and
handler access are unknown. Do not describe as a live exploit without those
facts.
- V08 `ServerGrantCurrency` (515-540) checks amount/reason presence and caps
amount at 10,000, but has no moderator/operator authorization check. It grants
to `currentPlayerId`. Severity: High if client-callable/deployed.
- `ReportPlayer` source uses hardcoded webhook URL; no server-owned permission
check. The helper for server-side mod state does not help unless the specific
action invokes it.
Confirmed vs configuration-dependent:
- Confirmed source flaw: moderator data is obtained from ordinary UserData and
the client uses it for gating; no trusted server permission check is present
in the cited Unity checks/actions.
- Confirmed source flaw: V08 ReportPlayer trusts caller-supplied reporter identity;
V08 ServerGrantCurrency lacks a role check.
- PlayFab documents client UpdateUserData as readable/writable by the client;
ReadOnlyData is server-modifiable and client-readable. A title policy may
restrict API access, but no dashboard access was performed. The Moderator key
is ordinary UserData in source, not ReadOnlyData.
- Unknown: whether V08 or any equivalent CloudScript revision is deployed,
whether these handlers can be called by a regular client in the live title,
and whether an external server/admin allowlist protects them.
Recommended trusted permission design (preserve moderator features):
1. Store roles in server-only InternalData or an authoritative role store; use
ReadOnlyData only if the client must display the role. Provision/revoke roles
through a restricted admin tool, never client UpdateUserData.
2. For every sensitive action, authorize on the backend using authenticated
`currentPlayerId` and server-owned role data. Never accept ReporterId, role,
target permission, or authority from client arguments. Validate target,
allowed action, reason, and scope; rate-limit and audit.
3. Expose a read-only role query for UI convenience, but backend checks remain
mandatory. Keep current moderator menus/features while migrating; hide UI on
denied response, and route kick/ban/grant actions through backend. A local
UI check is usability only.
4. Migrate existing moderators from the existing trusted roster/records after
verifying them; do not blindly promote all accounts whose writable key says
TRUE. Handle revocation and stale cache/session refresh.
2. CURRENCY CALLS AND REWARD PATHS
PlayFab distinction:
- Unity `PlayFabClientAPI.AddUserVirtualCurrency` is a client API call made with
the player session. It changes the server-stored balance; it is not merely a
local display change. If enabled for this title, a modified client can request
a caller-chosen grant. A local amount check/UI does not prove eligibility.
- `PlayFabServerAPI.AddUserVirtualCurrency` / CloudScript server API requires
server credentials and is appropriate only inside trusted backend logic.
V08 ServerGrantCurrency uses this privileged operation, but the handler itself
still needs caller authorization and validation. A privileged operation does
not make an exposed handler admin-only.
- Exact client API policy and live CloudScript exposure are dashboard/deployment
questions; neither is verified here.
Unity grant paths located in local source (Assets-relative paths):
- `Scripts/Oculus_Login.cs`: `AddCurrency(int)` calls client
`AddUserVirtualCurrency` with supplied amount and optimistically changes the
local balance; `RemoveCurrency` calls client `SubtractUserVirtualCurrency`.
- `Scripts/Atm_Manager.cs`: `BuyProductCallback` grants `currencyToGive` after
local Meta IAP success/SKU handling. Client callback is not independently
server receipt validation in the inspected caller.
- `Scripts/ShopKeeper.cs`: `SelectOre` grants 1/2/3 currency; `PickItem` checks
cached local balance then spends through `RemoveCurrency`.
- `Scripts/RedeemAfter24.cs`: grants 5 currency via `AddCurrency`.
- `Scripts/SpokesSigmaIAP.cs`: `GrantAll` grants configured/computed currency
and issues client `PurchaseItem`; `OWNED_IAP` dedupe is written via client
`UpdateUserData` (`AppendOwnedSku`).
- `Assets/BundleIAP.cs`: grants 1 through `Oculus_Login.AddCurrency`.
- Spend/purchase callers include `Scripts/BuyCosmetic.cs`, `BuyOnCollide.cs`,
`BuyBundleOnCollide.cs`, and `ShopKeeper.cs`. These are relevant compatibility
paths, though not all directly grant currency.
- Other writers: `Oculus_Login.CreateKeyIfNot` writes Moderator through client
`UpdateUserData`; `SavePlayerNameToUserData` writes USERNAME. Do not treat
client UserData as trusted for roles, receipt claims, or reward dedupe.
CloudScript/revision findings:
- V08 `ServerGrantCurrency` (515-540): amount/reason checks and cap only; no
role check or idempotency. Draft uses privileged server grant on caller.
- V08 `OnPurchaseItem` (302-380): accepts `args.Price`; catalog lookup is TODO;
no robust duplicate transaction protection. Not itself a Unity AddCurrency
call, but economy-critical.
- V08 `completedLevel` (179-199): accepts client level/kill values and writes
progression; no reward grant observed in this handler. It can still falsify
progression and must not become a trusted reward trigger without validation.
- V08 `unlockHighSkillContent` (254-265) relies on a PlayStream event; rule/
trigger configuration was not available, so execution path is unknown.
- V08 `UpdateUserInternalData`/server APIs are privileged, but no general
client-facing UpdateUserData handler was found in V08. CloudScript does not
secure client Unity calls that bypass it.
Severity assessment:
- High, source-confirmed pattern: currency grant amount and eligibility originate
from client callers (`AddCurrency`, IAP/reward callers). The balance is stored
server-side, but caller authentication is not equivalent to reward validation.
- High, conditional: arbitrary client AddUserVirtualCurrency succeeds in
production. Verify client API policy and PlayFab title settings before calling
it a confirmed live vulnerability. Disable client grants where the title
configuration permits; regardless, migrate every legitimate reward first.
- High, conditional: V08 ServerGrantCurrency callable by ordinary players and/or
deployed. Handler lacks authorization, so any permitted caller can grant up
to 10,000 HC per call. Rate limiting alone is insufficient.
- Medium/High: client-controlled IAP callback and UserData dedupe can allow
forged/replayed grants if there is no independent receipt validation and
idempotent server transaction. Live Meta/PlayFab receipt configuration unknown.
- Medium: local optimistic balance can be desynchronized and local shop decisions
bypassed; authoritative server balance still determines outcomes where a
server-priced purchase API is used.
3. SECURE REWARD AND MODERATOR-GRANT FLOWS
Player-earned rewards:
- Unity sends a minimal claim/event, not amount or balance. Backend authenticates
PlayFab session, derives player ID from verified context, validates reward
eligibility from server-owned state, caps per event/window, and writes a
durable idempotency record keyed by player + event/transaction ID before
granting. Handle retries atomically or through a transaction/outbox pattern.
- For gameplay rewards that cannot be proven by PUN Cloud, treat client reports
as claims subject to conservative limits/review, or use authoritative server
simulation. Do not promise secure anti-cheat from CloudScript alone.
- For daily/24-hour rewards, enforce cooldown and one-time claim server-side using
server timestamps and atomic/serialized updates. Client timers are display only.
- IAP: validate provider receipt server-side, map receipt/transaction ID to
product and configured currency amount, enforce unique transaction processing,
then grant once. Never accept `currencyToGive` from the client as authority.
- Shops: client requests product ID; backend resolves price from catalog and
performs purchase/deduction + grant with idempotency. Return authoritative
inventory/balance; preserve item IDs and existing UX.
Moderator grants:
- Backend derives moderator from server-only role store and caller from session;
checks operator permission, target, currency code, positive bounded amount,
reason, and per-operator limits. Require an idempotency/request ID, audit
caller/target/amount/reason/time/result, and return authoritative balance.
- Grant endpoints must not be directly callable without role authorization.
Keep title secret keys only in backend secret storage; never in Unity.
- Role changes and currency grants should be separate permissions; provide
revocation, review, and audit views. Do not expose broad admin grant powers to
ordinary moderators unless explicitly required.
4. DASHBOARD / DEPLOYMENT CHECKS (NOT PERFORMED)
- PlayFab Title Settings/API Policies: whether Client AddUserVirtualCurrency and
UpdateUserData are allowed/restricted. Do not infer from source or SDK presence.
- Player Data settings: verify client write behavior for Moderator and legacy
OWNED_IAP keys. Ordinary UserData is documented client-writable unless
restricted; ReadOnlyData/InternalData are distinct server-controlled stores.
- Automation > CloudScript: deployed revision and access to each handler; any
Azure Function auth/function secrets; title API policies and caller access.
- PlayStream rules/events: confirm whether unlockHighSkillContent or other reward
functions are triggered, and whether event fields are server-authenticated.
- Economy/catalog: currency codes, prices, products, purchase validation, and
whether existing clients can call direct grant APIs.
- Meta IAP: app/product configuration and server receipt validation path.
- None of these settings or deployed services were inspected or changed.
5. COMPATIBILITY AND REGRESSION TESTS
Stage only; preserve existing features and data:
1. Ordinary account attempts Moderator=TRUE via UpdateUserData; server role
response/actions remain denied. Legitimate moderator retains menus/actions.
2. Spoof ReporterId/TargetId in ReportPlayer; authorization uses authenticated
caller and rejects non-moderator; valid report still records correctly.
3. Call ServerGrantCurrency as ordinary account, forged moderator, and valid
operator; only authorized operation succeeds, with audit and authoritative
balance. Test over-limit, negative, malformed, and repeated requests.
4. Call Unity direct AddUserVirtualCurrency with arbitrary amount in staging;
verify intended API policy blocks it after migration. Verify legitimate ore,
daily reward, bundle, and IAP rewards still work exactly once.
5. Replay/retry identical reward, IAP receipt, moderator grant, and purchase
request; each transaction has one grant/deduction. Simulate timeout after
backend commit and retry.
6. Forge IAP callback/product/amount and reuse receipt; reject or grant only the
configured product once. Validate legitimate Meta purchase and restoration.
7. Check ShopKeeper balance/spend, cosmetics, bundle purchases, and currency UI
against server-returned balance; verify no negative or duplicate balance.
8. Verify existing moderators, player names, owned items, balances, IAP
entitlements, and migration/revocation behavior remain intact.
9. Confirm logs omit title secrets, receipts, and sensitive tokens; inspect audit
record completeness and rollback procedure.
6. OFFICIAL DOCUMENTATION CONSULTED
- PlayFab Client UpdateUserData is documented as client-readable and writable:
https://learn.microsoft.com/en-us/rest/api/playfab/client/player-data-management/update-user-data
- PlayFab player data modes (Client, Read Only, Internal):
https://learn.microsoft.com/en-us/gaming/playfab/player-progression/player-data/
- PlayFab Server UpdateUserReadOnlyData requires title secret and is server
written/client read-only:
https://learn.microsoft.com/en-us/rest/api/playfab/server/player-data-management/update-user-read-only-data
- PlayFab CloudScript/Azure Functions quickstart and function authorization:
https://learn.microsoft.com/en-us/gaming/playfab/live-service-management/service-gateway/automation/cloudscript-af/quickstart
- PlayFab legacy virtual currency documentation (Economy v1):
https://learn.microsoft.com/en-us/gaming/playfab/economy-monetization/economy/tutorials/currencies
- PlayFab Economy v2 currency quickstart:
https://learn.microsoft.com/en-us/gaming/playfab/economy-monetization/economy-v2/tutorials/currencies
SCOPE NOTE
V08 and bundle findings are draft/source observations, not proof of live
deployment. Source review does not establish dashboard access, PlayFab API
policy, or production behavior. No implementation or production changes were
made. This focused report supplements V14; it does not replace it.
END OF REPORT
|