Launch-readiness: reframe scope, security hardening, consent docs - #1
Launch-readiness: reframe scope, security hardening, consent docs#1dedmonwalkin wants to merge 1 commit into
Conversation
Product reframe - README leads with captions + AD Draft Assist; removed sign-language claim - New LIMITATIONS.md: honest about mock vs production, WER, AD human review, WFD/WASLI 2018 rationale for dropping the sign-language module Security - Path-traversal gate: JOB_ID_RE validation before every lookup; resolved upload paths asserted inside UPLOADS_DIR - Timing-safe API key compare; dropped ?api_key= query-string auth - TRUSTED_PROXY gate for X-Forwarded-For (no spoofed client IPs by default) - SSRF guard (safeFetch) refuses private/loopback/ULA; streaming response cap; allowHttpLocalhost for intentional local inference - MODEL_FALLBACK_ON_ERROR=false by default; fallback outputs tagged _provider: 'mock-fallback' so silent degradation is visible - Magic-byte sniff on uploads (mp4/webm/wav/ogg/mp3) — declared MIME must match bytes; stream cleanup on failure + orphan-dir sweep - 1MB cap on JSON bodies with 413 response (drain, don't destroy) - Loud warn on empty API_KEYS when AUTH_DISABLED not set - Snapshot retention (default 20) to bound postgres growth - Dockerfile: non-root 'app' user, tini as PID 1 Architecture - Config validation with parseNumber/parseBool, fail-fast + cached - structuredClone in state serializer to decouple snapshots from live Map - Pipeline cancellation check per chunk (JobCancelledError) - SSE progress replay on attach + 15s heartbeat + cleanup on close - .unref() on snapshot/cleanup intervals so process exits cleanly Cookies & consent - COOKIES.md with 4 categories (strictly-necessary / functional / analytics opt-in / advertising never) - PRIVACY.md: GDPR table, controller vs operator, CCPA note - SECURITY.md: disclosure via GH Security tab, SLAs, operator hardening - /cookies and /privacy routes render shared-layout markdown Tests - test/jobService.test.js rewritten with dynamic import inside before() so AUTH_DISABLED/PORT reach runtimeConfig before it caches - New tests: cookie/privacy routes, security headers, path-traversal rejection, job-id regex, unsupported file type, magic-byte mismatch, 413 on oversize body All 75 tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b835e2bee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (parsed.protocol === 'http:' && !(allowHttpLocalhost && isLocalhostName)) { | ||
| throw new SsrfError(`Refusing plain http to non-localhost host: ${host}`); |
There was a problem hiding this comment.
Permit loopback IPs for local HTTP model endpoints
createHttpModelProvider and createLibreTranslateProvider set allowHttpLocalhost for 127.0.0.1 and ::1, but assertSafeUrl only treats localhost/localhost.localdomain as eligible for plain HTTP. As a result, configs like MODEL_HTTP_BASE_URL=http://127.0.0.1:11434 (or IPv6 loopback) are rejected with Refusing plain http to non-localhost host, breaking common local/offline deployments that previously worked with loopback IPs.
Useful? React with 👍 / 👎.
| if (err instanceof PayloadTooLargeError) { | ||
| return sendJson(res, 413, { error: 'Request body too large', limit_bytes: err.limit }); | ||
| } | ||
| const status = err.message.includes('Missing required') || err.message.includes('Unsupported file') || err.message.includes('do not match declared type') ? 400 : 500; |
There was a problem hiding this comment.
Return 400 for short media uploads failing magic-byte checks
parseUpload now throws File too short to validate for allowed MIME uploads under 12 bytes, but the top-level error mapper does not include that message in its client-error list, so these malformed uploads are reported as 500. This misclassifies a user input validation failure as a server fault and makes client handling/retries incorrect.
Useful? React with 👍 / 👎.
Summary
Four parallel work streams to take this from prototype to something we can honestly put in front of users.
1. Product reframe
LIMITATIONS.md: honest WER numbers, human-review requirement for AD, explicit removal of the sign-language module citing the WFD/WASLI 2018 position against signing avatars for machine-generated content2. Security hardening
JOB_ID_RE = /^job_[0-9a-f-]{36}$/gate on every job endpoint; resolved upload paths asserted insideUPLOADS_DIRcrypto.timingSafeEqualkey compare; dropped?api_key=query-string fallbackX-Forwarded-Forignored unlessTRUSTED_PROXY=truesrc/utils/safeFetch.jsrefuses private/loopback/ULA/multicast/link-local after DNS resolution; streaming response cap;allowHttpLocalhostauto-enabled for local inferenceMODEL_FALLBACK_ON_ERROR=falseby default; fallback outputs tagged_provider: 'mock-fallback'ftyp, EBML, RIFF/WAVE, OggS, ID3/MPEG sync); stream cleanup on failuresweepOrphanDirsdeletes failed-upload directories older than TTLappuser,tinias PID 1API_KEYSwhenAUTH_DISABLEDis not set3. Architecture
parseNumber/parseBool, fail-fast + cachedstructuredClonein state serializer to decouple snapshots from liveMapmutationJobCancelledError).unref()on snapshot/cleanup intervals so the process exits cleanly4. Cookies & consent
COOKIES.md— 4 categories (strictly-necessary / functional / analytics opt-in / advertising never)PRIVACY.md— GDPR table, controller-vs-operator distinction, CCPA noteSECURITY.md— disclosure via GH Security tab, 3/7/90-day SLAs, operator hardening checklist/cookiesand/privacyroutes render shared-layout markdownTest plan
npm testpasses — 75 tests, 6 suites, 0 failuresLIMITATIONS.mdwording before any public launch post🤖 Generated with Claude Code