A distribution problem
You should not have to run an executable to get a poster.
SteamPanno turns a Steam library into a visual poster. The original ships as a downloadable binary. A Reddit thread found the obvious fault line. This fork was about working out what the safer alternative actually required.
SteamPanno turns a Steam library into a visual poster. The original ships as a binary. The Reddit thread found the problem in the first reply.
Not because the developer was malicious. The project is open source, the intent looks honest, and the poster idea is genuinely good. But "you can audit the source if you want" is not a real answer for a one-time creative tool. Most users are not going to build from source, reason through Steam API access, and run a personal risk calculation to get a poster. They are going to look at the download page and say no.
The problem was not the code. It was the distribution shape. A binary that touches a Steam identity, even lightly, is the wrong packaging for something that is supposed to be fun and low-stakes.
The use case is: give me a visual of my library. The trust ask is: run this executable from the internet. Those two things are not matched.
I forked it not to compete with the original. The idea was already done. The question I wanted to work through was practical: what does moving this to a server actually require, and what does that change about the trust model?
The obvious first answer is a static site. That stops working the moment you try to sign in.
Steam's sign-in is OpenID 2.0. The flow sends the user to Steam's login page and back to a callback URL you control. That callback has to post back to Steam to verify the assertion, extract the SteamID, and issue a session. GitHub Pages cannot receive that return trip. The verification logic has nowhere to run. The moment you need a real OpenID callback, you need a real server.
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 you have a real server and a working callback, the session is product surface, not
just implementation glue. The user just handed you their Steam identity. The session
cookie needs httpOnly and secure flags or it is not doing its
job. httpOnly keeps the token off JavaScript. secure keeps
it on HTTPS. sameSite narrows cross-site cookie behavior. None of those are optional
when the user has just authenticated.
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 cannot live in client code or a committed config file. The session
secret has the same requirement. Render handles both without you committing either one:
SESSION_SECRET gets generateValue: true so the host creates
it on first deploy, and STEAM_API_KEY gets sync: false so
you set it once in the dashboard. Neither ends up in version control.
services:
- type: web
name: steampanno-web
env: docker
dockerfilePath: webapp/Dockerfile
envVars:
- key: SESSION_SECRET
generateValue: true
- key: STEAM_API_KEY
sync: false GitHub Actions runs typecheck, builds the Docker image on every push to main, and pushes to GHCR. Not comprehensive coverage, but it keeps the container buildable and the TypeScript honest.
The stopping point was Steam developer/API identity setup. Getting the hosted path clean meant registering and configuring the API key like a real product dependency, not treating it like a throwaway local secret. For a side experiment whose point I had already worked out, it was enough friction to stop. The fork showed what the web path required. I did not need it to ship to know what it demonstrated.
Moving a tool from binary to browser does not just change the deployment shape. It changes the trust question the user is being asked. Instead of "will I run this executable from a stranger," the question becomes "what does this site need from my Steam account, and what does it actually keep?" That is a better-shaped question. Not risk-free, but answerable. The poster idea was fine from the start.
The distribution model was the product decision that needed to change.