Apply these rules when reviewing the security implications of any code change in this project. The framing is OWASP Top 10 mapped onto this project's specific stack — Next.js App Router, Payload CMS (SQLite/Turso, Vercel Blob), Remark/Shiki markdown, Vercel hosting, Sentry, Mixpanel.
Secret and Environment-Variable Handling
See secret-handling.md for:
- No literal secret committed (Payload secret, Sentry DSN with auth token, Vercel Blob token, Turso auth token, test user password)
process.env.*accessed only inside the project's whitelisted env-access filesNEXT_PUBLIC_*prefix used only for values intentionally exposed to the browser.env.localis gitignored; example only in.env.example
Input Validation
See input-validation.md for:
- All
route.tshandlers validate and coerce request inputs before passing them to Payload orfetch - All
params/searchParamsare treated as untrusted (they reach the server asPromise<unknown>-shaped at runtime) - Repository
whereclauses receive sanitized values (no array-coercion bypass onslug) - Repository return values are parsed through the Zod schemas in
app/(app)/_/repositories/payload-types.tsbefore reaching components
Access Control
See access-control.md for:
- Each Payload collection / global has an explicit
accessconfiguration appropriate for its data sensitivity - Draft content is gated by the
_status: { equals: "published" }filter whendraftis not requested - The
userscollection's auth lock settings are not weakened - New
route.tsmutation endpoints (e.g., the/posts/cachesrevalidation route) are protected against unauthenticated abuse
Privacy and Exposure Control
See privacy-and-exposure.md for:
- Draft, preview, admin-only, and unpublished content cannot leak through public routes, metadata, JSON-LD, sitemap, robots, or image routes
- Public media URLs expose only intentionally public assets and do not reveal private storage tokens or internal identifiers
- Sentry and Mixpanel changes do not capture unnecessary PII, secrets, draft content, or CMS-authored private fields
- Client-exposed environment variables, analytics event properties, and error context are intentionally public
XSS in Markdown Rendering
See xss-in-markdown.md for:
- The
remarkParseallowDangerousProtocol: truesetting inapp/(app)/_/helpers/markdown.tsis acknowledged and any new HAST element mapping does not pass user-controlled URLs intodangerouslySetInnerHTMLor unsanitized attributes - Custom directive nodes (currently
webembed) only emit attributes that React will encode - New components added to the renderer's
defaultComponentsmap encode untrusted children safely rehypeReactis not bypassed (no manual HAST-to-string interpolation that skips React's encoding)
SSRF and Embed Fetching
See ssrf-and-embeds.md for:
getWebEmbedMetadata(whichfetch-es arbitrary URLs from CMS-authored content) does not let internal-network URLs through to production- The
Imagecomponent'sunoptimizedflag is not used when the URL is user-controlled and could point at internal hosts - New CMS-author-controlled URLs that flow into a
fetchcall go through an allowlist or a hostname check - New
images.remotePatternsentries innext.config.tsare tightly scoped
Auth and Session Management
See auth-and-session.md for:
- Payload's auth lockout settings (
lockTime,maxLoginAttemptson theuserscollection) are not relaxed - Live preview / draft state is reachable only through Payload's auth path, not via a query-string bypass
- Sentry
sendDefaultPii: trueexposure is acknowledged when adding new identifiers/contexts
Supply Chain
See supply-chain.md for:
- New npm dependencies justify their addition per development-guidelines › change-management
- New dependencies are reasonably popular, maintained, and platform-agnostic
- Lockfile is updated; transitive additions are inspected for known-vulnerable versions
- No
postinstall/preparescript in a new dependency runs unexpected code