closes#49
Records the P0 manual test pass in `TODO.md` per its Workflow section
(checked-off results into Completed Steps; cache size management and
eviction promoted to Next Step). `TODO.md` is the only changed file —
no production code changes, as the issue requires.
## Test setup
`pixad` built from `main` at `6573b9d` via `make build`, run on port
18099 with a throwaway local config (temp state dir, known
`signing_key`, `allowlist_hosts` including `s3.sneak.cloud`), driven
with curl using explicit cookie replay (session cookies are
`Secure`/`HttpOnly`/`SameSite=Strict`).
## Results — all six checks PASS
1. **Login form**: GET `/` → HTTP 200, `Pixa - Login` page with
`name="key"` password form.
2. **Wrong key error**: POST `/` with `key=wrong-key` → HTTP 200 login
page containing "Invalid signing key".
3. **Generator form**: POST `/` with the correct signing key → HTTP 303
to `/` with `Set-Cookie: pixa_session=...; HttpOnly; Secure;
SameSite=Strict`; GET `/` with that cookie → `Pixa - URL Generator`
with the `/generate` form and logout link.
4. **Encrypted URL serves image**: POST `/generate` (ttl=3600) produced
a `/v1/e/<token>/img.jpeg` URL → HTTP 200, `Content-Type:
image/jpeg`, 800x600 baseline JPEG, 61706 bytes.
5. **Expired URL → 410**: a ttl=1 URL fetched after 3 s → HTTP 410 Gone
with `{"error":"URL has expired","status":410,...}`.
6. **Logout**: GET `/logout` → HTTP 303 to `/` with `Set-Cookie:
pixa_session=; Max-Age=0`; subsequent GET `/` → login form again.
Additionally, all nine checks in `scripts/manual-test.sh` passed
against the same server instance.
## Verification
`make check` green on the branch head (all tests, golangci-lint 0
issues, fmt-check clean) — the first fully green `make check` on a
`main`-derived branch under the current linter, confirming the #47/#48
fix on merged `main`.
Note for review: the test execution was performed this session; the
adversarial re-review (independently re-running the six flows) is still
pending and should happen before merge.
Co-authored-by: sneak <sneak@sneak.berlin>
Reviewed-on: #50
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
A config value that is set but unparseable or invalid now aborts
startup with an error naming the offending key and value; defaults
apply only to omitted keys. Unknown top-level config keys and unknown
metrics subkeys abort startup naming each unknown key, so typos like
whitelist_hosts fail immediately instead of being silently ignored. A
config file that exists at a standard location but fails to parse is
now a fatal error instead of being skipped with a warning. state_dir
is verified creatable and writable with a probe file before the
listener binds. Port must be in 1-65535 (fractional values are
rejected, not truncated), upstream_connections_per_host must be at
least 1, allowlist_hosts entries must be bare hostnames, sentry_dsn
must be a valid URL when set, and metrics credentials must be set
together. The stale signing_key comment in config.example.yml (keyless
mode was never implemented) now states the actual requirement.
TODO.md records the completed step per its Workflow section.
closes#47
Fixes the two remaining `gosec` findings on `main`, both `G124`
(http.Cookie missing or has insecure `Secure`, `HttpOnly`, or
`SameSite` attribute):
- `internal/session/session.go:84` (`CreateSession`, the login
set-cookie path)
- `internal/session/session.go:128` (`ClearSession`, the logout
delete-cookie path)
## What changed
- Both cookie-writing paths now unconditionally set `Secure: true`,
`HttpOnly: true`, and `SameSite: http.SameSiteStrictMode`.
- The `secure` field (previously wired to `!config.Debug`) and the
`sameSite` field are removed from `session.Manager`, and the dead
secure-toggle parameter is removed from `session.NewManager`, which
now takes only the signing key (reviewer-directed; the mechanical
call-shape updates in `session_test.go` leave every assertion
untouched).
- TDD per repo rules: the first commit adds
`TestSessionCookieAttributesAlwaysSecure` (failing), asserting that
every cookie emitted by the session manager carries `HttpOnly`,
`Secure`, and `SameSite` of Lax or stricter, for both write paths.
The second commit makes it pass.
- `TODO.md` updated per its Workflow section (Next Step completed,
next Future Step promoted, stale "10 open findings" Status text
corrected).
## Attribute choices and reasoning
- `Secure: true` always: the `G124` analyzer only accepts a constant
`true` store, and there is no legitimate configuration in which the
authentication cookie should be sent over plaintext HTTP. The old
behavior disabled `Secure` whenever `debug` was on. Local development
over `http://localhost` keeps working: browsers treat `localhost` as
a trustworthy origin and accept `Secure` cookies there. Any
plain-HTTP flow on a non-localhost host will no longer keep a
session, which is the point of the fix.
- `SameSite: Strict` (unchanged from current production behavior, and
stricter than the Lax minimum): the login form is a same-origin POST
to `/` followed by a same-site redirect, so `Strict` breaks nothing.
- `HttpOnly: true` (unchanged).
## Verification
`make check` (tests, golangci-lint, fmt-check) is fully green on the
branch head `cb9e14e`: all tests pass and the linter reports 0 issues,
independently confirmed by the reviewer in a fresh worktree. Commit
history: `ca15f52` (failing test) → `02ca16a` (fix + TODO.md, closes
#47) → `cb9e14e` (drop the dead `NewManager` parameter).
Co-authored-by: sneak <sneak@sneak.berlin>
Reviewed-on: #48
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
closes#24
## QA Audit Fixes
This PR addresses issues found during the 1.0/MVP QA audit.
### Changes
1. **TODO.md: Mark AVIF encoding as done** — AVIF encoding is fully implemented via govips in `processor.go` but was still listed as a TODO item.
2. **scripts/manual-test.sh: Fix form field names** — The manual test script was using wrong field names:
- Login form: was sending `password=...`, should be `key=...` (matching the HTML form's `name="key"`)
- Generator form: was sending `source_url`, `fit_mode` — should be `url`, `fit` (matching the handler's `r.FormValue()` calls)
- This means **the manual test script never actually worked** — login always failed silently because the `key` field was empty.
### Full QA Audit Results
The comprehensive QA audit report has been posted as a comment on [issue #24](#24).
Co-authored-by: user <user@Mac.lan guest wan>
Reviewed-on: #25
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
Uses github.com/gen2brain/webp - a CGO-free library that uses WASM via
wazero runtime for encoding. WebP decoding was already supported.
- Add gen2brain/webp dependency for encoding
- Implement WebP encoding in processor.go
- Add FormatWebP to SupportedOutputFormats
- Re-enable WebP option in generator form dropdown
- Mark WebP encoding as complete in TODO.md
- check: default target, runs fmt-check, lint, and test
- fmt-check: verifies code is properly formatted
- fmt: formats code with gofmt
- lint: runs golangci-lint
- test: runs go test
- build: builds pixad binary with version info
- clean: removes build artifacts