Bug check on main: hunt what the tests are not catching #6
Found one while you were working, and it is the big one. Add it to the sweep.
One client can take a seat on both teams at once, which defeats vision entirely.
I opened four websockets from a single process against the live app. Each one was handed its own seat, alternating sides:
bot-red-0/red bot-blue-0/blue bot-red-1/red bot-blue-1/blue
distinct seats held simultaneously: 4
Then two sockets, one per side, compared snapshots:
socket 1 (red) sees 5 players
socket 2 (blue) sees 5 players
union of what ONE cheater sees across two sockets: 10 of 10
So the server-side vision work — which is genuinely correct, per socket — is bypassed by joining the other team and reading its snapshot. Every enemy position, continuously, with no forged packets and nothing malformed: just two honest connections. It is also a seat-denial vector, since ten sockets take the whole match and leave real people with nothing.
The cause is that open() in src/server.ts hands out a seat per connection, and a connection is anonymous — nothing ties two sockets to the same person. Every other app on this forge identifies a visitor with one opaque HttpOnly cookie; this one does not.
What the fix needs to decide (say which you chose in the pull): a person holds at most one playing seat. The straightforward version is the cookie the other apps use — mint it on the page, read it on upgrade, and refuse a second playing seat for a session that already has one. Extra connections should still be allowed to watch (they are useful — a second tab, a phone), but a watcher must be given a spectator snapshot built for no team, not a seat with a side, or you have moved the hole rather than closed it. Whatever you choose, a test should hold two sockets for one session and assert the second gets no seat on the other side.
Two smaller things from the same probe, both non-fatal:
- Garbage survives cleanly: bad JSON, wrong types, a 5KB message,
NaN/Infinitycoordinates, and a 100-message burst all left the server serving and my player sane. Good. - But
angleis stored unvalidated — I senta: 99999and the snapshot came back"angle": 99996.3. Trig makes it harmless today; normalise it anyway, since it is the value the flash's facing check reads.
valis merged and live at https://val--forge-apps.benjivers.com. Before anything else is built on it, go throughmainlooking for what is actually broken. This is your job, not a feature pass: do not add capabilities, do not change how anything looks, do not rename anything a player sees.Where the bugs are likeliest
The rules are pure functions and they are where a wrong number hides quietly:
The boundary, which is not optional
Every output escaped, every socket message bounded and validated, nothing trusted from a client that the server should decide, all state under
FORGE_APP_DATA, no secret in the repository, and the security floor insrc/server.tson every response. The socket takes JSON from anyone on the internet: check what a malformed, oversized, or wrong-typed message does, and what happens if a client sends 500 messages a second.How to work
For anything you find: fix it in the smallest way that closes it, and add a test that fails against the code as it was. Prove the test bites — break the line it covers, watch it go red, put it back. Say in the pull exactly what a player or an attacker could have done before the fix.
If you sweep it and find nothing real, say that. A clean report honestly given is a useful result; inventing findings to look busy is the failure mode of this job. Either way the pull description should list what you checked, not only what you changed.
Not bugs, and not yours to fix here: there is no client-side prediction, vision is a full circle rather than a view cone, there is no overtime at 12–12, and bots commit to a site rather than rotating on sound. Those are known and deliberate.