GET /api/devices/:id/screenshot returns a live picture of what a screen is showing, but
it was still authorized pre-tenancy: `device.user_id !== user.id`, with a role bypass
listing 'admin'/'superadmin'. Three consequences, all now covered by tests:
- `device.user_id &&` SHORT-CIRCUITED. A device with no user_id — never paired, or its
owner deleted — skipped the ownership test entirely, so any authenticated account on the
instance could read it. An unpaired panel displays its pairing code on screen, so that
image is also a route to claiming the device (AUTH-10, out of scope here but connected).
- 'platform_admin' was absent from the bypass list. #14 renamed 'superadmin' to
'platform_admin', so an actual platform admin fell through to the ownership test and was
denied unless they happened to own the row.
- Workspace members other than the owner were denied a device they administer through
every other endpoint.
Now uses accessContext() against the device's workspace — the same helper routes/devices.js
uses — which covers direct membership, org-level access and platform staff in one call. A
device with no workspace is denied outright rather than defaulting open.
Deliberately unchanged: the ?token= query-parameter mechanism on this route, which is a
separate finding with its own blast radius.
No response shape change: still 200 / 401 / 403 / 404 with the same bodies.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>