Security & correctness audit remediation (+ Perry-runtime compat) - #2
Conversation
Perry's bcrypt is ~8s of CPU at cost-12 even after the typed-array perf fixes (PerryTS/perry #5544/#5551; remaining gap tracked in #5525) vs ~250ms on Node — too slow for a login request. cost-10 is ~2-3s, fine for a single admin login; raise back once perry's bcrypt nears native speed. NEW hashes only — the seeded admin password must be re-hashed at the new cost. (Earlier this branch also carried an async→sync bcrypt swap and a parseCapabilities Buffer guard; both proved unnecessary once PerryTS/perry #5485/#5521/#5563 landed — async bcrypt works and the @perryts/mysql JSON column already decodes to an object.) Claude-Session: https://claude.ai/code/session_012nEhNZCXKSeNStMnVD5E9e
Full audit of the CMS across auth, authz, injection/XSS/SSRF, caching, the job queue, schema evolution, CI/build, and repo hygiene. Findings are grouped by severity with file locations, impact, and fixes; the following commits remediate them.
Auth/session: - TOTP fails closed on both login paths (was: any 6-digit code accepted) - session cookie Secure derived from X-Forwarded-Proto + HSTS/nosniff/ frame-options/referrer-policy headers - session tokens stored sha256-hashed at rest (migration 0002 clears the old plaintext rows); timing-equalized login; invalidate sessions on password change Authz: - role/user management allowlists capabilities and blocks built-in-role edits and assigning a role more privileged than the actor, so manageRoles/manageUsers no longer escalate to admin - content status is no longer mass-assignable (publish gate); admin content and form-submission views enforce read/readDrafts/viewSubmissions; GET /api/v1/settings filters sensitive keys; readOthersDrafts enforced Injection/XSS/SSRF: - @skelpo/site-kit renderMarkdown/renderTipTap sanitize raw HTML and block javascript:/data: URLs - webhook SSRF guard (private/loopback/link-local + metadata) and fetch timeout; media upload size cap + nosniff/attachment on /raw; email HTML escaping; redirect-destination validation Correctness: - cache dep-key invalidation, TTL, eviction cleanup, compute-vs-invalidate guard; cursor pagination no longer loops (MyDateTime serialization) - job handler timeout, recurring maintenance, scheduledPublish producer, unhandled-rejection guards; boot-time non-UTC-DB warning - schema evolution: reject ambiguous rename diffs, migrate-before-validate, advance schemaRevision, optimistic locking on content updates - quote/comment-aware migration splitter + advisory migration lock Portability: - replace instanceof Response with a shared isResponse() guard across every API route (Perry-safe; closes a latent fail-open) Adds unit tests for content XSS, privilege escalation, and the SQL splitter.
- tsconfig -> NodeNext (root + both packages): a missing .js import extension is now a compile error instead of a runtime break - bump hono 4.12.21 -> 4.12.27 (clears GHSA-88fw-hqm2-52qc and related); drop unused @hono/node-server dependency; ignore uploads-*/ so a customer's local upload dir can't be committed - test.yml: compile dist + boot-smoke /healthz + integration tripwire (fails when 0 tests run or all are skipped); fix the test:integration exit-code masking so a failing suite actually fails CI - release.yml: install from the workspace root + a pre-publish unit-test gate; remove the stale per-package lockfiles - carry forward in-progress Perry-runtime working changes (CLAUDE.md, seed.ts, build-perry.sh, media-pipeline.md)
…egration suite - add WEBHOOK_ALLOW_PRIVATE_HOSTS (default off) so deployments/tests that intentionally deliver to an internal host can opt out of the SSRF guard; enable it in the integration test env (tests hit a local 127.0.0.1 sink) - fix a stale integration test that POSTed to a bulk /admin/settings route that no longer exists — settings are edited per-key at /admin/settings/:keyName; the old CI masked this failure - align the admin per-key settings save with the settings:all cache umbrella (was still using the old no-op invalidate) Full suite now green locally: 77 unit + 34 integration.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (55)
📝 WalkthroughWalkthroughThis PR hardens authentication, session, and authorization controls (timing-safe login, hashed sessions, fail-closed TOTP, privilege-escalation guards, draft/publish authorization), adds XSS and SSRF protections, fixes cache/content/migration correctness bugs, reworks the server for Perry-native compilation, updates build/CI tooling, and adds two new documentation files. ChangesSecurity Hardening
Estimated code review effort: 5 (Critical) | ~120 minutes Caching and Content/DB Correctness
Perry-Native Runtime and Build Tooling
Documentation
Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthRoute
participant Password
participant Sessions
Client->>AuthRoute: POST /login (email, password)
alt user not found
AuthRoute->>Password: verifyDummy()
else user found
AuthRoute->>Password: verifyPassword(hash)
end
AuthRoute->>Sessions: createSession(token)
Sessions->>Sessions: sha256Hex(token) stored as id
Sessions-->>AuthRoute: raw token (for cookie)
sequenceDiagram
participant AdminAPI
participant Dispatch
participant Fetch
AdminAPI->>Dispatch: createWebhook(url)
Dispatch->>Dispatch: assertSafeWebhookUrl(url)
AdminAPI->>Dispatch: deliverWebhookJob(hook)
Dispatch->>Dispatch: assertSafeWebhookUrl(hook.url)
Dispatch->>Fetch: fetch(url, AbortSignal.timeout)
Fetch-->>Dispatch: response or timeout
Related Issues: None referenced in the diff. Related PRs: None referenced in the diff. Suggested labels: security, breaking-change, needs-deep-review Suggested reviewers: Reviewers familiar with auth/session internals, cache invalidation, and Perry runtime constraints should review this PR given its scope and security-critical nature. Poem A rabbit hopped through hashed session lanes, ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Full audit of the CMS followed by remediation across auth, authz, injection/XSS/SSRF, caching, the job queue, schema evolution, CI/build, and repo hygiene. The audit report is in
fable-audit.md.Highlights
Security
SecureviaX-Forwarded-Proto+ HSTS/nosniff/frame headers; session tokens stored sha256-hashed at rest (migration0002clears old rows); login timing equalized; sessions invalidated on password change.manageRoles/manageUsersno longer escalate to admin.statusno longer mass-assignable (publish gate); admin content & form-submission views enforceread/readDrafts/viewSubmissions;GET /api/v1/settingsfilters sensitive keys.@skelpo/site-kitrenderMarkdown/renderTipTapsanitize raw HTML and blockjavascript:/data:URLs; webhook SSRF guard (+ opt-inWEBHOOK_ALLOW_PRIVATE_HOSTS) and fetch timeout; media upload size cap +nosniff/attachment on/raw; email HTML escaping; redirect-destination validation.Correctness
scheduledPublishproducer, unhandled-rejection guards.schemaRevision, optimistic locking on content updates; quote/comment-aware migration splitter + advisory migration lock.instanceof Responsewith a sharedisResponse()guard across every API route (Perry-safe; closes a latent fail-open).Build / CI
hono4.12.21 → 4.12.27 (clears CVEs); dropped unused@hono/node-server; ignoreuploads-*/.test.ymlcompilesdist/, boot-smokes/healthz, and fails when integration tests don't actually run; fixed thetest:integrationexit-code masking.release.ymlinstalls from the workspace root + a pre-publish test gate.0002clears the sessions table — everyone re-logs in once after deploy (old plaintext tokens can't match a hashed lookup).changesobject (422 otherwise) — the guard against silent data loss.Tests
77 unit + 34 integration, all green locally.
Follow-ups (not in this PR)
Pin GitHub Actions to commit SHAs; remaining DB-dependent integration tests for bearer auth / job concurrency; doc corrections listed in the audit.
Summary by CodeRabbit
New Features
Security
Bug Fixes