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
257
|
VOIDERATION V23: OWNER DASHBOARD & INCIDENT CONTROL CENTER
Date: 2026-10-30
Scope: Research/planning only. No code, dashboard, API or production changes.
Baseline: V22, V21, V19 plus local Worker/Unity source. Dashboard is proposed;
no local dashboard, D1 incident store or owner review API currently exists.
DESIGN DECISION
The local website is the primary control center and system of record UI.
Discord is optional, secondary notification only. Authority and state remain in
trusted backend/D1, never in browser storage, Unity or Discord. Proposed shape:
Unity/Photon detections + Worker-observed auth/heartbeat signals
|
v
Cloudflare Worker API
authenticate / validate / authorize owner
| | |
v v v
D1 alert outbox Photon auth gate
incidents, (optional) checks current
restrictions, | restriction at
audit v each admission
Discord
Owner browser --same-origin HTTPS/loopback--> local dashboard service
| authenticated TLS API
+----------> Worker API
The local process explicitly binds `127.0.0.1:<port>`; never `0.0.0.0`.
It serves UI and a narrow local API, holds no PlayFab/Worker/Discord secret in
JavaScript, and calls Worker over TLS using an owner/device credential kept in
OS-protected local storage. Worker revalidates owner authority on every action.
The browser never connects directly to D1. Alternative Worker-hosted UI/public
Pages is out of scope because owner website must remain local-only.
CURRENT STATUS
- CONFIRMED: Worker has no D1 binding, incident/restriction routes, durable alert
outbox, or owner dashboard API. Existing Discord interactions verify Discord
signatures and OWNER_ID for their commands, not local website sessions.
- CONFIRMED: existing Worker `logMessage` catches Discord errors; `/export`,
`/setup` and Photon state use query-string credentials. Do not reuse those
patterns for dashboard auth. Current dashboard/incident features are absent.
- CONDITIONAL: design can use Worker+D1 for durable enforcement and a local
service for presentation, with polling. Requires implementation and staging.
- UNVERIFIED: production Worker version/bindings, D1 readiness/quotas, Photon
custom auth and owner identity/device provisioning. No dashboard access made.
PAGES / LAYOUT
1. Overview
- Summary cards: online players (label source/time; unknown if stale), pending
incidents, active PendingReview/ConfirmedRestricted players, recent owner
actions. Lists latest high-severity incidents and integration health.
- Do not infer online status from a missed heartbeat; show stale/unknown and
source (Photon webhook vs heartbeat). No buttons that mutate state here.
2. Live alerts
- Poll new incident IDs with severity, rule/version, UTC time, confidence,
evidence quality, player reference and current restriction. Filters and
unseen/read marker are presentation-only. All detections are persisted even
if tab closed; alerts are not a transient-only stream.
3. Incident review
- Detail: canonical player ID, session/build/mode, rule/version, evidence and
source/provenance, detected/received timestamps, confidence caveats, linked
incidents, prior decisions and current restriction. Redact tokens, receipts,
raw IP and unnecessary pose data. Evidence must show client-reported vs
backend-verified provenance.
4. Pending approval
- Queue by age/severity/rule. Show explicit action consequences and required
owner reason. `Approve/Clear` and `Reject/Confirm` use separate confirm
dialogs; stale records require reload. Approval/rejection never silently
issues a permanent ban.
5. Player management
- Search canonical PlayFab ID; list active restrictions, source incident,
expiry, history and ban status. Explicit `Release` and separate `Ban` /
`Unban` (only if supported by policy) actions; require reason and confirmation.
No one-click bulk release/ban in initial version.
6. Detection settings
- Versioned rule configuration: enabled, incident severity/confidence policy,
evidence source, alert policy, and `require_owner_review_before_rejoin`.
Defaults: movement/latency/tracking/Tag proximity log+incident, no hold;
only repeated server-verifiable protocol abuse may be configured for
temporary review hold. Show proposed/current values and change audit.
Threshold changes need owner re-auth and should be staged/versioned; never
accept settings from game clients.
7. Audit history
- Append-only view of action/actor/time/target, before/after incident and
restriction state, reason, request ID, result/error, and source. Filters and
export with sensitive fields minimized. Include denied, stale and failed
mutation attempts. The browser has no edit/delete API for audit records.
STATE AND APPROVAL SEMANTICS
Keep incident review status separate from restriction state and any PlayFab ban.
- Incident status: `PendingReview` -> `Approved` or `Rejected`; explicit owner
release/resolution may set incident `Released` as an outcome. `Released` is
not an account-access flag.
- Restriction: `None`, `PendingReview`, `ConfirmedRestricted`, `Released`, or
separately recorded `Banned`, with source incident, timestamps and optional
expiry. Store current access restriction separately from incident status.
- Detection creates exactly one `PendingReview` incident per idempotency key and
always writes before alerting. Only rules explicitly configured for
review-before-rejoin can set a PendingReview restriction; default movement and
behavioral heuristics do not.
- Owner `Approve/Clear`: mark incident Approved and atomically release its
PendingReview restriction. Owner `Reject/Confirm`: mark Rejected; if a hold
exists, retain it as ConfirmedRestricted. ConfirmedRestricted remains until
explicit owner `Release` or a separate audited ban decision. Do not make
rejection itself an automatic permanent ban.
- Every decision is server-authorized, conditional on current status/version,
idempotent by request ID, transactional across state and audit. Reject stale
or conflicting updates; audit their attempted result. Owner reason required.
- Photon restriction applies to every fresh authentication/reconnect. V22/V21
confirm this is not a reliable targeted kick for an existing PUN Cloud session;
that player may remain connected until disconnect. Dashboard must display this
limitation and never show “kicked” unless independently confirmed.
API / DATA REQUIREMENTS
Proposed Worker API (all authenticated/validated; exact route names TBD):
- Read: `GET /owner/summary`, paginated incidents/alerts, incident detail,
player restrictions/history, rule config, audit page, service/alert health.
- Mutations: `POST /owner/incidents/{id}/approve`, `/reject`; player
`/release`, `/ban` and supported `/unban`; versioned rule config. Require
reason, expected version and idempotency key. Never accept owner/player
identity as authoritative request fields.
- Detection intake: authenticate the session, derive player identity, validate
event schema/size/rate/provenance, ignore client-supplied reviewer/status or
authoritative severity. Record every accepted detection. Do not treat
`/updateplayers` (currently no-op) as detection intake.
- Photon custom auth: validates the PlayFab Photon token with the supported
provider contract, derives canonical PlayFab ID, checks active restriction and
returns documented Photon success/denial. Photon token is distinct from
PlayFab SessionTicket. Exact Worker-to-PlayFab proxy wire contract remains an
explicit staging gate; do not guess. Remove Unity's 20-second anonymous
fallback and close every other unauthenticated connect route before claiming
rejoin enforcement.
- D1 logical data: incidents (unique source/idempotency key, evidence/provenance,
timestamps, rule/config version, status); player restriction (one authoritative
active state per player, source, expiry/version); append-only owner audit;
rule versions; optional alert outbox (incident/dedupe key, attempts, next retry,
sent/failed state). Atomic D1 batch/transaction for review state + restriction
+ audit. Minimal evidence/retention; no auth tickets/tokens or secrets.
OWNER LOGIN / LOCAL WEB SECURITY
- Bind only loopback; use same-origin local UI/API and reject all non-loopback
interfaces. HTTPS for local API if supported by chosen host/runtime; otherwise
use a secure loopback-only transport with strict origin/host checks and no
mixed content. TLS is mandatory from local service to Worker.
- Localhost is not authentication. Require a per-install owner credential or
device-bound login provisioned out-of-band; do not expose the Worker credential
to JS/localStorage. Keep it in OS credential store, rotate/revoke, and require
re-authentication for release/ban/rule changes. Short-lived sessions, Secure
(when HTTPS), HttpOnly, SameSite=Strict cookies; no long-lived bearer token in
browser storage. Owner identity is bound server-side, not passed in request.
- For state-changing requests require CSRF token plus Origin/Host allowlist and
same-site checks; reject unexpected Host, Origin, scheme and forwarded-host
headers. Do not treat CORS as CSRF protection. DNS rebinding defense: literal
loopback binding, strict Host allowlist (localhost/127.0.0.1 only as configured),
validate browser Origin and reject arbitrary hostnames; test actual browser and
OS firewall behavior. Prevent clickjacking with frame restrictions.
- Backend independently verifies owner role on every read of sensitive evidence
and every mutation; UI-hidden buttons have no security value. Rate-limit login
and mutations; generic login errors; protect against session fixation; invalidate
sessions on logout/credential rotation. Local OS compromise remains out of
scope; document trusted-machine assumption and recovery process.
- No PlayFab developer secret, Cloudflare API token, Worker secret, Discord bot
token or webhook URL in HTML/JS/source maps/browser storage. Local service uses
narrowly scoped owner credential; Worker secrets stay server-side. Avoid
leaking credentials or evidence in logs, URLs, crash reports or exports.
POLLING, OFFLINE AND RECOVERY
- Start with simple authenticated polling (e.g. every 3-5 seconds while visible,
slower/backoff when hidden or offline), cursor-based incident/audit pages and
bounded payloads. Return monotonic event cursor/updated timestamp; dedupe by ID.
WebSockets/SSE are unnecessary until measured polling load/latency demands them.
- Show last successful sync, stale indicator, backend/D1 status and pending
optional Discord-delivery state. On offline: show cached read-only data with
age; disable all mutation controls, never queue/replay approve/reject/release/
ban actions. Reconnect refreshes authoritative state; no optimistic success.
- Detection persistence and Photon admission are backend duties, not dependent
on owner browser being open. If Worker/D1 unavailable, alert/dashboard reports
service outage; new Photon auth with an enabled restriction gate returns
retryable failure (fail closed, no anonymous fallback); existing sessions are
not mass-kicked. Discord outage does not block D1 persistence or dashboard.
- Backups: define D1 export/snapshot cadence and retention, encrypt/access-limit
stored exports, keep schema migrations/versioned config separately, and test
restore in staging. Document restore ordering for incidents, restrictions,
audit and outbox; after restore reconcile queued/sent alerts and never replay
owner mutations. Audit is append-only through API; backups do not make it
tamper-proof against a compromised account. Keep recovery credentials offline
and rotate after suspected compromise.
DISCORD AND COST
- Discord is optional secondary critical notification. Durable incident + local
dashboard remain authoritative if Discord is disabled/down. Default: notify
only owner-configured critical/high-confidence server-verified incidents and
system-health failures; low-confidence movement detections remain visible in
dashboard but can be aggregated/suppressed in Discord. If owner instead wants
every alert, use rate limiting/aggregation.
- Persist before notification; outbox provides at-least-once retries, bounded
backoff and dedupe. Dashboard shows pending/sent/failed. Payload only incident
ID, UTC, rule/version, confidence, short caveat and restriction state; no
credentials, raw IP, proof nonce, receipts or unnecessary identifiers. Verify
private destination and least-privilege bot. Never use Discord for approval.
- Local hosting has no added cloud hosting cost. Worker/D1, dashboard polling,
auth checks and outbox share existing Cloudflare quotas; V19's 100-CCU heartbeat
estimate (28,800 requests and ~28,800 base D1 row updates/day) excludes dashboard
polls, incidents and auth. €0 additional recurring is plausible on Free tier,
not guaranteed until account plan and aggregate usage are measured. No paid
push service is needed for initial polling design.
IMPLEMENTATION DEPENDENCIES / ACCEPTANCE
1. Owner decides local host/runtime, credential provisioning/recovery, session
timeout, review policy, retention/backup cadence, and whether Discord critical
alerts are enabled. Confirm dashboard is trusted-machine/owner-only.
2. Verify Worker deployment, D1 binding/plan/headroom, Discord bot/destination,
owner identity, and Photon Realtime/Voice settings read-only.
3. Prove Photon custom-auth proxy contract in staging; distinguish Photon token
from SessionTicket. Confirm no anonymous fallback/admission and reconnect/Voice
behavior. Without proof, dashboard may manage incidents but cannot claim
restriction enforcement on Photon rejoin.
4. Implement trusted detection intake, D1 migrations/transactions, rule settings,
owner API/auth, and audit first; then local UI/polling and optional outbox.
5. Implement rejoin restriction gate only after staging validates every auth path.
No reliable live-session kick is promised. Preserve all ScreechVR features.
Minimum tests: non-owner/forged owner ID denied server-side; CSRF, bad Origin/Host,
DNS rebinding and LAN access rejected; session expiry/logout/revocation; stale and
duplicate action idempotency; approve/reject/release/ban distinct states and audit;
dashboard offline prevents mutation; incident persists before alert; Discord off,
429/5xx/outage retries; 100-CCU plus polling quota measurement; restricted fresh
login/room rejoin denied, owner release restores access; D1 outage gives retryable
auth failure and no anonymous downgrade; existing connected session limitations
displayed; Tag/Infection, PVP, Voice, movement, items, welding/Sandbox, horror
levels, /link, MOTD, CCU and reconnect regressions pass.
UNRESOLVED BEFORE IMPLEMENTATION
- Local framework/port/certificate approach and owner credential enrollment/recovery.
- Worker owner auth model, D1 availability/transaction behavior and tested backups.
- Exact Photon custom-auth adapter compatibility and all anonymous/reconnect/Voice
settings; no live PUN targeted-kick guarantee.
- Which rules truly require pre-rejoin hold, hold expiry/review SLA/appeal and
retention policy. Recommended default is no movement-based restriction.
- Actual Worker/D1 quotas; local dashboard polling/incident volume and alert policy.
Evidence reused: V22, V21, V19, Stage 0, consolidated pre-implementation audit,
local `worker.js` and Unity Photon auth source. No dashboards or production
services were accessed.
END OF REPORT
|