Tony GorbProduct Designer & Builder

A second device is a second user: every button needs an owner

I let people open Scenri on their phone with a six-character code. Three days later the overnight QA called the release healthy, while its security section had barely run. The phone could still reach the Danger zone.

In this story6 sections
  1. Your app had one user. Now it has two
  2. The report said healthy
  3. What the phone could still do
  4. The fix is a decision per action
  5. A green with a hole in it is red
  6. Before you let a second device in

On 23 September 2026 Scenri learned to open on a phone. Settings shows a QR code; scan it on the same Wi-Fi and the studio opens. Any device other than the computer running Scenri brings a six-character code, which rides inside the QR code. It worked on the first try, and that was the problem I had not noticed yet.

Your app had one user. Now it has two

Scenri is local-first. It runs on your computer, keeps your library there, and holds your provider keys. Until that release, every request came from the person sitting at that computer, so there was nothing to decide about who could do what. Everything was theirs.

A phone holding the code is a different user. It might be you on the sofa. It might be a phone you lent someone, or a tablet left signed in for 400 days, which is how long the access cookie lasts. The code proves the device was invited. It does not prove the device should be allowed to do everything the host can.

The phone release did think about some of this. The listener only opens on private Wi-Fi or Ethernet addresses, never a VPN or a virtual machine. Ten wrong codes from one address in ten minutes and that address waits. Allow Scenri, which changes the firewall, can only be pressed on the host. During review, Reveal and Add to desktop moved from hidden on a phone to refused by the server, because hiding a button is not the same as refusing the request behind it.

But nobody had walked through every action and asked the same question of each one.

The report said healthy

Three days later an overnight release QA ran ten sessions against version 0.18.0. Its answer at 09:00 opened with "v0.18.0 ... is technically healthy. No blocker (S1) was found in anything that could be run tonight."

The last six words carry the weight. Nine of the ten sessions passed the bar on independent re-scores, between 95 and 100. Security scored 47. The README explains why:

Its dynamic tests and its report rewrite were blocked by the auto-mode safety classifier, and nobody tried another way round.

Release QA README, 26 Sep 2026

The live phone tests in the security session were never run. The report was written mostly from reading code. And the headline still said healthy.

I think this is the most common way a QA process lies: not with a false result, but with a missing one that gets averaged in. A section that could not run looks like a section with nothing to report.

What the phone could still do

The gap closed from two directions. The responsive session, which did test Local access live over the Wi-Fi address, had already opened Settings on a phone and found the Danger zone there: Delete shots and Delete everything, for every brand, offered to a device holding only the code. The reviewer who re-checked the security report found four more actions it had filed under that known issue without naming them: shut Scenri down, install a staged update, restart into one, and change or remove the spend cap.

Put together, a phone holding the code could:

  • wipe the library, or every brand in it
  • set and clear provider keys
  • shut Scenri down, or install and restart into an update
  • change or remove the monthly spend cap
  • read absolute paths on the host

None of these needed a clever attack. They needed the code, which the product hands out by design.

The fix is a decision per action

The fix was not one switch. It was a list. For every route that changes something, I asked one question: is this the household's, or the host's?

Making shots, refining, keeping and browsing belong to whoever is using the studio. A phone should do all of it, which is the point of the feature. Anything that touches the machine itself, the money, or every brand at once belongs to the host.

The pattern already existed on eight routes, one line at the top of each handler:

if (!fromThisComputer(req)) return reply.status(403).send({ error: 'Only on the computer running Scenri.' });

fromThisComputer checks both that the request names a loopback host and that it arrived on a loopback socket. The fixes went in through the night and morning of 26 September and shipped by lunchtime in PR #255, a patch release from main that held only security and cost fixes so users got them before the next feature release:

Security score; other sessions passed
47 / 100
Sessions passed; the report read healthy
9 of 10
From phone access shipping to the patch
3 days

The other half was the interface. Refusing on the server is the rule, but a phone that offers a button and then fails is a broken product. So the phone studio no longer offers shut down or update controls, and spend caps are shown read-only there. On the phone, the cap is information. On the host, it is a control.

A green with a hole in it is red

Two rules came out of this, and I use both on anything I build now.

The first is about design. The moment a second device can reach your app, you have a permission model whether you wrote one or not. Write it on purpose, one action at a time.

The second is about QA. A check that could not run is not a pass and not a neutral. It is red until someone runs it another way. A report that says "no blocker in anything that could be run" has told you exactly where to look.

Before you let a second device in

  1. List every route that changes state. All of them, not the ones you remember.
  2. Mark each one: the household's, or the host's. Wipes, keys, money, shutdown and updates are the host's.
  3. Refuse on the server first. Hide in the interface second.
  4. Show host-only settings read-only on the second device, so it still explains itself.
  5. Give the host a way to sign every other device out. Scenri's New code replaces the code and does that.
  6. In your QA summary, list what did not run above what passed.

Tony Gorb, Product Designer & Builder

Questions about this story, or a design role to talk about? Write to bytonygorb@gmail.com or find me on LinkedIn.

More stories