Vol. XV / Issue 01

The McKinnie Dispatch

Filed from the experiment desk

Experiment SteamPanno Trust boundary

Trust in small tools

A cool tool can still ask the wrong question.

SteamPanno turns a Steam library into a visual poster. The fork was not about owning that idea. It was about the moment people looked at a Steam-related executable and said the obvious thing: why is this not a website?

SteamPanno is the kind of small project that exposes a bigger truth: software does not only fail when the code is bad. It fails when the trust ask is absurd.

The surface idea was harmless and fun. Turn a Steam library into a visual poster. Show people their gaming history in a way they can understand at a glance. That is a good little product idea.

The Reddit thread got interesting because the audience immediately found the fault line. Not the layout algorithm. Not the poster. Not the Steam API. The trust model.

A random executable that touches your Steam identity is a wild thing to ask strangers to run for a one-time image.

Even if the project is open source. Even if the developer is honest. Even if Windows Defender is throwing a false positive. None of that fixes the first thirty seconds of user trust. Normal people are not going to audit the source, compile the app, verify the binary, reason through Steamworks API permissions, and then decide whether the poster is worth it.

They are going to say the obvious thing: why is this not a website?

That is why I forked it. Not because I needed to own the idea. I did not. The original idea was already there. I wanted to explore the scarier question underneath it: what would this look like if the product stopped asking the user to trust the wrong thing?

The trust boundary moved into code.

The web path was not just a hosting change. Steam sign-in needs a callback, Steam API keys need custody, and browser sessions need real handling.

Steam sign-in is not a static-page problem The browser path still needs a callback URL. That is where GitHub Pages stopped being enough.
export function steamLoginUrl(returnTo: string, realm: string): string {
  const p = new URLSearchParams({
    "openid.ns": "http://specs.openid.net/auth/2.0",
    "openid.mode": "checkid_setup",
    "openid.return_to": returnTo,
    "openid.realm": realm,
    "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
    "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select"
  });

  return `https://steamcommunity.com/openid/login?${p.toString()}`;
}
A session is part of the product surface If the user signs in, the session cannot be treated like demo glue.
const token = await makeSession(steamid);
const isProd = process.env.NODE_ENV === "production";

res.cookie("session", token, {
  httpOnly: true,
  sameSite: "lax",
  secure: isProd,
  maxAge: 7 * 24 * 60 * 60 * 1000
});
Secrets belong on the server The deployed path keeps the Steam API key out of the browser and lets the host generate the session secret.
services:
  - type: web
    name: steampanno-web
    env: docker
    dockerfilePath: webapp/Dockerfile
    envVars:
      - key: SESSION_SECRET
        generateValue: true
      - key: STEAM_API_KEY
        sync: false

That does not make the web version magically safe. Websites can be malicious. Hosted software has its own trust boundary. A login-looking flow can become phishing if it is handled badly.

But it changes the shape of the risk. Instead of asking, "Will I run this executable from the internet?" the user can ask, "What account data does this site need, where does the session live, and can I get the result without handing over more than necessary?"

The more interesting version might not need sign-in at all for the common case. If the user has a public profile, maybe a Steam profile URL is enough. Save sign-in for advanced features. Keep the first trust ask as small as the first use case.

That is also where I stopped. Not because the web path was impossible. Publishing it cleanly meant doing Steam developer/API identity setup I did not expect to need. For a serious product, that is just part of the work. For a side experiment whose point was already clear, it was enough friction to shelve it.

The poster was the shiny object. The trust boundary was the product.