← back to Codex Review 2026 04 30

consensus/resize-it.md

61 lines

# Consensus — resize-it
Generated 2026-04-30T23:48:03-07:00. Source: codex review/fix/rereview/round-2 artifacts (round-3 was quota-blocked).

## Codex pass-1 review (top risks)
## Top Risks (P0 — fix this week)
- [server.py:21](/Users/stevestudio2/Projects/resize-it/server.py:21), [server.py:22](/Users/stevestudio2/Projects/resize-it/server.py:22), [.env:1](/Users/stevestudio2/Projects/resize-it/.env:1), [.env:2](/Users/stevestudio2/Projects/resize-it/.env:2): Spoonflower credentials are committed/hardcoded defaults. Rotate password, remove defaults, require env vars at startup.
- [SKILL.md:89](/Users/stevestudio2/Projects/resize-it/SKILL.md:89): Gemini API key is embedded in docs/source. Revoke it, move to secret storage, scrub history if this was ever shared.
- [server.py:270](/Users/stevestudio2/Projects/resize-it/server.py:270)-[274](/Users/stevestudio2/Projects/resize-it/server.py:274): Flask debug server binds `0.0.0.0` and advertises a public IP. Disable debug, bind localhost or put behind authenticated production WSGI.
- [server.py:29](/Users/stevestudio2/Projects/resize-it/server.py:29), [server.py:103](/Users/stevestudio2/Projects/resize-it/server.py:103), [server.py:155](/Users/stevestudio2/Projects/resize-it/server.py:155): no auth on endpoints that use stored Spoonflower credentials. Add owner auth/token gate; especially block public `/api/upload-to-spoonflower`.
- [server.py:165](/Users/stevestudio2/Projects/resize-it/server.py:165)-[167](/Users/stevestudio2/Projects/resize-it/server.py:167): URL validation is substring-only; `http://internal/?spoonflower.com` passes to Playwright. Use `urlparse`, require HTTPS and exact Spoonflower host suffix.
- [server.py:190](/Users/stevestudio2/Projects/resize-it/server.py:190)-[197](/Users/stevestudio2/Projects/resize-it/server.py:197): picks newest image from global `/tmp`; concurrent requests can resize/download the wrong customer file. Use per-request temp dirs and explicit script output paths.


## Round-1 applied
## Applied
- `server.py:23` — removed hardcoded Spoonflower credential defaults and now fails fast when `SPOONFLOWER_EMAIL` or `SPOONFLOWER_PASSWORD` is missing — prevents source fallback secrets.
- `server.py:18`, `server.py:66`, `server.py:154`, `server.py:197`, `server.py:226` — replaced hardcoded `/root/.claude/skills/resize-it` subprocess cwd values with `Path(__file__).resolve().parent` — makes scripts run from this checkout.
- `server.py:119`, `server.py:174` — replaced `request.json` with `request.get_json(silent=True)` plus a 400 response for missing or malformed JSON — avoids 500s on bad request bodies.
- `server.py:291` — changed advertised URL to localhost — removes public-IP guidance for a local tool.
- `server.py:292` — changed Flask bind from `0.0.0.0`/`debug=True` to `127.0.0.1`/`debug=False` — avoids exposing the debug server.
- `SKILL.md:89` — replaced embedded Gemini API key with `process.env.GEMINI_API_KEY` and a fail-fast check — removes the secret from docs/source.
- `.env.example:1` — added placeholder environment variables for Spoonflower credentials and Gemini API key — documents required configuration without storing real secrets.


## Codex re-review verdict: FIX BEFORE SHIP

## New issues introduced
`server.py:33` — missing Spoonflower env vars now raise during module import, so `/` and `/api/resize` cannot run without Spoonflower credentials even though local resize does not use them — P1

`server.py:23` — credentials are read only from process env; existing local `.env` will be ignored because no dotenv loader is present — P1


## POKE deep-dive (this project had a self-found regression or crash)
## Failure mode
Starting/importing `server.py` fails before `/` or `/api/resize` can run when `SPOONFLOWER_EMAIL` or `SPOONFLOWER_PASSWORD` is unset. The local resize path does not use Spoonflower credentials, but import-time validation blocks it anyway. `server.py:23-34`, `server.py:41-67`

## Root cause
`server.py:33-34` — the module raises `RuntimeError` during import if either Spoonflower env var is missing. That code runs before route handlers are usable, while the local upload/resize handler only shells out to `scripts/resize_image.py` and does not need those credentials. `server.py:23-34`, `server.py:41-67`

## Origin
REGRESSION — `CHANGES.md:2` says the recent patch removed hardcoded Spoonflower defaults and made startup fail fast when env vars are missing. `CODEX_REREVIEW.md:5` and `CODEX_REREVIEW.md:20` identify that as broken because unrelated local resize usage is blocked.

## Proposed patch
`server.py:26-34`
```diff
- missing_env = [
+ def missing_spoonflower_env():
+     return [

## Still deferred for Steve
- P0 — `.env:1` — real Spoonflower credentials may still exist locally — deferred because `.env` was explicitly left untouched and real credentials must be rotated by Steve.
- P0 — `SKILL.md:89` — exposed Gemini API key needs revocation and history scrubbing if shared — source was cleaned, but rotation/history cleanup needs Steve.
- P0 — `server.py:41`, `server.py:115`, `server.py:170` — no auth on local API endpoints that can use stored credentials — deferred because auth design/token ownership is outside the safe fix list.
- P0 — `server.py:183` — Spoonflower URL validation is substring-only — deferred because stronger host validation was not in the safe fix list.
- P0 — `server.py:208` — newest-image selection from global `/tmp` can cross requests — deferred because request-scoped temp workflow changes are broader than safe surgical fixes.
- P1 — `server.py:52` — uploaded files can overwrite the same sanitized filename — deferred because filename/session behavior was not in the safe fix list.
- P1 — `server.py:277` — download route can expose matching files in `/tmp` — deferred because allowlist/session mapping is a broader behavior change.
- P1 — `scripts/spoonflower_full_workflow.py:201` — upload success is inferred after sleep/screenshot — deferred because adding confirmed save-state assertions needs workflow testing.
- P1 — `web-interface.html:263` — frontend reads `error.message` while server returns `error` — deferred because public-facing HTML changes were out of scope.