Security: require auth on entry/photo mutations (IDOR fix) + constant-time compare + CI - #5
Merged
Merged
Conversation
…-time compare + CI Fixes an IDOR on the write endpoints and hardens password/token comparison. - Add requireAuth(req) in src/lib/auth.ts validating the owner session cookie (lj_session) and failing closed (401) when SITE_PASSWORD is unset, so public READ can stay open but public WRITE cannot. - Enforce requireAuth at the top of every mutation handler: entries [id] PATCH, and entries [id]/photos POST and DELETE. - Replace non-constant-time string comparisons in the login route and proxy with crypto.timingSafeEqual on equal-length Buffers (length-guarded). - Add GitHub Actions CI (lint/build/test + npm audit + gitleaks); repo had none. Claude-Session: https://claude.ai/code/session_01BAMbBXXNhnUcztWtqTHzmu
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a high-severity IDOR / public-write vulnerability on the memory mutation endpoints, hardens password/token comparison against timing attacks, and adds CI (the repo had none).
Vulnerability (HIGH — IDOR / unauthenticated write)
The mutation handlers acted on a caller-supplied entry
idwith no per-handler authorization:src/app/api/entries/[id]/route.ts—PATCH(edit a memory's description)src/app/api/entries/[id]/photos/route.ts—POST(add photo),DELETE(remove photo)The only gate was the optional
SITE_PASSWORDproxy (src/proxy.ts), which returnsNextResponse.next()when the password is blank (the documented default = public read). On the default config, anyone could edit or delete any entry or photo by guessing/enumerating ids — a classic Insecure Direct Object Reference, made worse by the endpoints being fully unauthenticated.Vulnerability (LOW — non-constant-time comparison)
src/app/api/auth/login/route.tsandsrc/proxy.tscompared the password / session token with!==/===, which short-circuits and can leak length/prefix information via timing.Fix
requireAuth(req)helper insrc/lib/auth.tsvalidates the owner session cookie (lj_session, the same token the login flow issues) and returns 401 when absent/invalid. Crucially, ifSITE_PASSWORDis unset there is no owner session to validate, so writes are rejected (401) rather than falling open. It is called at the top of every mutation handler (entries PATCH,photos POST,photos DELETE). GET/read handlers are untouched — public reads still work.timingSafeStringEqualusescrypto.timingSafeEqualon equal-length Buffers (with an explicit length guard, sincetimingSafeEqualthrows on unequal lengths). The login route and proxy now use it instead of!==/===.CI
Added
.github/workflows/ci.yml(lint + build + test on Node 20, plusnpm auditand gitleaks secret scan). The repo previously had no CI.Verification
npm run build— passes (including the Proxy/Middleware edge bundle;node:crypto'stimingSafeEqualbuilds fine there).npm run lint— clean.npm test— 38/38 pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01BAMbBXXNhnUcztWtqTHzmu