Vol. XV / Issue 01

The McKinnie Dispatch

Filed from the experiment desk

Experiment Fork story Trust model

A distribution problem

You should not have to run an executable to get a poster.

Semyon Kirnosenko's original SteamPanno project turns a Steam library into a visual poster and publishes desktop downloads. I forked the GPL-licensed project to find out what a web version would actually require.

SteamPanno turns a Steam library into a visual poster, but the original ships as a binary. The problem showed up in the Reddit thread's first reply: why should making a poster require running a downloaded executable?

This wasn't an accusation against the original developer. Open source let me inspect the code, but most people won't build it and trace its Steam access just to get a poster. They'll decide whether the download is worth the trust it asks for.

The distribution shape creates that problem. A fun poster tool still asks the user to run a binary and give it Steam library information.

I wanted a picture of my library. The download asked me to run an executable from the internet. That's too much trust for a poster.

I wasn't trying to compete with the original. It already had the poster idea. I forked it to answer a narrower question: what does moving it to a server require, and how does that change what the user has to trust?

A static site looked like the easy answer. Steam sign-in ruled it out.

Steam sign-in uses OpenID 2.0. It sends the user to Steam's login page, then back to a callback URL you control. The callback posts to Steam to verify the assertion, extracts the SteamID, and issues a session. GitHub Pages can't receive that return trip or run the verification. The OpenID callback needs a server.

Exhibit A: The callback requirement The login redirect is easy. Steam's return to your callback, followed by server-side verification against Steam's endpoint, is the part a static site can't handle.
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()}`;
}

Once the callback works, the session becomes part of the trust ask. The user just handed the server a Steam identity. The cookie needs httpOnly to keep the token out of JavaScript and secure to keep it on HTTPS. sameSite narrows cross-site cookie behavior. Those flags aren't optional after authentication.

Exhibit B: Session as product surface A signed-in session needs all three cookie controls. Otherwise the web version just moves the trust problem to another layer.
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
});

The Steam API key can't live in client code or committed config. Neither can the session secret. Render keeps both out of the repository. SESSION_SECRET uses generateValue: true, so the host creates it on first deploy. STEAM_API_KEY uses sync: false, so you set it once in the dashboard. Version control gets neither value.

Exhibit C: Secrets out of version control The host generates the session key on first deploy. You set the API key once in the dashboard. Version control sees neither.
services:
  - type: web
    name: steampanno-web
    env: docker
    dockerfilePath: webapp/Dockerfile
    envVars:
      - key: SESSION_SECRET
        generateValue: true
      - key: STEAM_API_KEY
        sync: false

The workflow ran typecheck when the webapp or its workflow changed. On pushes to main or master, it also built the Docker image and pushed it to GHCR. That wasn't comprehensive coverage, but it caught bad TypeScript and broken image builds.

I stopped at Steam developer and API identity setup. A clean hosted path required registering and configuring the API key as a product dependency, rather than treating it like a throwaway local secret. By then the fork had shown what the web path required. I didn't need to ship it to know what it demonstrated.

A browser version asks for a different kind of trust. The question changes from "will I run this stranger's executable?" to "what does this site need from my Steam account, and what does it keep?" The web version still has risk, but those are questions the product can answer. The poster idea was fine from the start.

The poster idea was sound. I couldn't justify the executable.