Files
MoHadiShibli 200e2bfc0b Show who's signed in on each controller, and an Invite QR code
When the PS4 reports a user signed in on a controller, the service asks for
the user's name and adds it to the status as `user`. The PS4 may not have
finished signing the user in by then, so a failed look-up is retried every
half second for five seconds. The tile shows the name in place of
"Controller N" (the ring keeps the number), and the controller screen reads
"Controller 1 · Alex". Names are kept out of the log, since people post logs
in bug reports, and they go out cleaned: quotes and backslashes escaped, and
control characters or anything that isn't valid UTF-8 replaced, because a
browser drops the whole WebSocket on a text frame that isn't valid UTF-8.

An Invite panel, opened from a button next to the settings or from the menu,
shows the page's address as a QR code for friends to scan. The console makes
the code with the QR encoder the app already ships, from the address the page
reached it on, and sends the module grid; the page draws it as SVG, so it still
needs no libraries.

The gamepad picker's tooltip had the same "Controller 1 · Controller 1" doubling
the controller screen had; it now names the user too.
2026-10-06 01:05:33 +03:00

85 lines
4.1 KiB
Markdown

# Security
## Reporting a problem
Report anything you find through GitHub's private vulnerability reporting on this
repository (**Security → Report a vulnerability**), not in a public issue.
## What Control4Free assumes
Control4Free turns a phone or PC into a controller for your PS4 over your own
network. There is no pairing, no password and no encryption, by design: a party
guest should be able to open a link and play.
**So treat the console's port 4264 the way you treat your TV remote.** Anyone who
can reach your PS4 over the network can:
- take a free controller and play, on the home screen, at sign-in and in games;
- press PS and Share, which reach the system menus;
- stop Control4Free, which disconnects every controller;
- see the names of the users signed in on the controllers.
They cannot sign in as one of your users without someone at the PS4 choosing that
user on the PS4's own screen, and they cannot see your screen.
Run Control4Free on a network you trust. Do not forward port 4264 through your
router, and do not run it on open or guest Wi-Fi. When you are finished, stop it
from the launcher app or the controller page, or turn the console off.
## What is defended
Being open to your network is not the same as being open to the internet. These
are the limits the code does enforce.
**Websites are kept out, with one deliberate gap.** A page on the internet that you
visit can make your browser send requests to your PS4, so the service refuses the
ones that matter:
- A WebSocket handshake carrying another site's `Origin` is refused with 403, so an
ordinary page's script cannot open the control socket.
- The page is only served for a literal IP address or `localhost` in the `Host`
header, so a domain name that resolves to your console (DNS rebinding) gets 403.
- The launcher's own `GET /api/status` and `POST /api/stop` require the header
`X-Control4Free-Launcher: 1` and refuse any request that carries an `Origin` at
all. A browser only sends a custom header cross-origin after a CORS preflight,
which this service never answers.
- The page is served with a Content-Security-Policy that keeps it to its own
resources.
The gap: a handshake with `Origin: null` is accepted. That is what a browser sends
for a controller page opened from a saved file, and the saved file is how gamepads
work in browsers that only allow the Gamepad API on secure pages. A website can
produce the same `Origin: null` from a sandboxed frame, so a page you have open
while on your home network could take a free controller and press buttons, as
anyone on your network can. It cannot take a controller someone is using, sign in
as a user, or see your screen.
**One controller has one owner.** A slot is claimed by one WebSocket connection.
Another device asking for the same slot is refused (409), and input never claims a
slot implicitly. A client that leaves has its buttons released at once.
**Input is validated.** Button masks, axes, trigger values and touch coordinates
are range-checked before they reach the virtual device, and a request that is not
well-formed JSON of the expected shape is rejected rather than guessed at.
**The service cannot be made to grow.** At most 4 controllers and 8 connections
exist at a time, each connection has fixed-size buffers, and a request body larger
than those buffers is refused.
**Stopping from the page is deliberate.** The page's Stop is refused while another
device owns a controller, so one player cannot cut another off by accident. The
launcher's `POST /api/stop` has no such guard, because the app on the console is
how you stop a service nobody is using any more. Its source address is not
checked: the app's own sandbox does not reliably appear as `127.0.0.1`, and anyone
on the network can already stop Control4Free from the page.
**The host process is put back.** GoldHEN runs the payload inside an existing
system process. Control4Free raises that process's credentials for the virtual
device calls and restores exactly what it saved before it exits, and it refuses to
touch them at all if it could not save them first.
## Versions
Fixes go into the next release; there is no separate maintenance branch. Use the
most recent release.