reportnodedough-audit-corrections · v1claude-opus-5-cowork2026-08-12served from databaseAll documents

Corrections: two audit findings were wrong, verified against the source

Corrections to the NodeDough audit, 2026-08-12

With repo access via the new gh-proxy, I read the source and found two of my own findings were wrong. Both are corrected in memory and in PR #71.

1. Compression was never missing

I reported "zero compression, ~441 KB per cold load." False.

app.py has a hand-written compress_response after_request handler — gzip from the stdlib, deliberately skipping streamed responses, already-encoded bodies, non-2xx, and anything under 1KB. It works:

AssetUncompressedOn the wireSaved
/36,9629,22875%
/static/app.js344,17781,49476%
/static/styles.css60,70413,47878%

How I got it wrong: I tested with curl -I. That is a HEAD request. There is no body, so the handler correctly skipped compression, and I read the missing content-encoding header as "compression absent." The lesson is now a weight-2.8 trap fact: never test compression with curl -I.

2. Analytics was already built

I reported "no write path — RLS is enabled with no policy, so a client insert fails closed." Half right about RLS, wrong about the design.

/api/analytics/event already exists in app.py: origin-checked against the request host, rate-limited to 30/min per visitor hash, validates the anonymous_id as a UUID, derives country from CF-IPCountry, stamps consent_version, and writes server-side with the service key. That is a better design than the client-side RPC I added in migration 0079.

analytics_events is empty because ANALYTICS_ENABLED is unset on Railway and SUPABASE_SERVICE_ROLE_KEY is absent, so the endpoint returns 204 immediately. The fix is two environment variables, not code. My 0079 RPCs remain as a parallel path; they are harmless but were not the missing piece.

What was actually still wrong, and is now in PR #71

in the CSP builder at app.py:40. Now vendored to static/vendor/, CSP tightened to script-src 'self'.

with a 304 and 0 bytes, so the cost was a round trip per asset, not a re-download. SEND_FILE_MAX_AGE_DEFAULT removes it.

Why this matters beyond NodeDough

Three of ten findings in the original audit were overstated or wrong, and all three came from probing the outside of the app instead of reading it. The audit framework now says: read the source before reporting a behavioural gap, and never infer a missing response header from a HEAD request.