Files
wiki/.cursor/rules/sec-check.mdc
Wayne Sutton 462729de58 chore: prepare v1.0.0 for Netlify deployment
Update version to 1.0.0 across package.json and changelog. Configure netlify.toml with Convex deployment URL (agreeable-trout-200.convex.site). Verify TypeScript type-safety for src and convex directories. Confirm Netlify build passes with SPA 404 fallback configured. Update TASK.md with deployment steps and files.md with complete file structure.
2025-12-14 11:30:22 -08:00

287 lines
7.8 KiB
Plaintext

# Security Guidelines for markdown-blog
This document covers security patterns specific to the markdown-blog application, a Convex-powered blog with no authentication.
## App-Specific Security Context
### Architecture
- **Frontend**: Vite + React 18.2 SPA (client-side only)
- **Backend**: Convex.dev (serverless database and functions)
- **Hosting**: Netlify with edge functions
- **Auth**: None (public blog)
### React Server Components Vulnerabilities
**Status: NOT AFFECTED**
This app does NOT use React Server Components and is NOT affected by:
- CVE-2025-55182 (Remote Code Execution)
- CVE-2025-55184 (Denial of Service)
- CVE-2025-55183 (Source Code Exposure)
These vulnerabilities affect apps using:
- `react-server-dom-webpack`
- `react-server-dom-parcel`
- `react-server-dom-turbopack`
This app uses standard React 18.2.0 client-side rendering with Vite bundler.
For the latest information, see:
- https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components
- https://react.dev/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components
## Database Tables
| Table | Contains PII | Public Access | Notes |
| ------------ | ------------ | ------------- | ---------------------------------- |
| `posts` | No | Read-only | Blog content |
| `viewCounts` | No | Write via API | View counter per post |
| `siteConfig` | No | Internal | Site settings (not currently used) |
## 1. Public API Security
### Query Functions (Read-Only)
All queries in this app are intentionally public for blog content:
```typescript
// Public queries - safe for public access
export const getAllPosts = query({...}); // List published posts
export const getPostBySlug = query({...}); // Get single post
export const getViewCount = query({...}); // Get view count
```
### Mutation Functions
| Function | Risk Level | Notes |
| -------------------- | ---------- | -------------------------------- |
| `syncPostsPublic` | Medium | Build-time sync, no auth |
| `incrementViewCount` | Low | No rate limiting, but low impact |
### syncPostsPublic Security Consideration
The `syncPostsPublic` mutation allows syncing posts without authentication. This is intentional for build-time deployment but has security implications:
```typescript
// Current: No auth check
export const syncPostsPublic = mutation({
args: { posts: v.array(...) },
handler: async (ctx, args) => {
// Syncs posts directly
},
});
```
**Mitigations in place:**
1. Mutation only affects the `posts` table
2. Posts require specific schema (slug, title, content, etc.)
3. Build-time sync uses environment variables
**Recommendations:**
- Consider adding CONVEX_DEPLOY_KEY check for production
- Monitor for unusual sync activity in Convex dashboard
## 2. HTTP Endpoint Security
### XSS Prevention
All HTTP endpoints properly escape output:
```typescript
// HTML escaping for Open Graph
function escapeHtml(text: string): string {
return text
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
// XML escaping for RSS feeds
function escapeXml(text: string): string {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
```
### CORS Headers
API endpoints include CORS headers for public access:
```typescript
headers: {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "public, max-age=300, s-maxage=600",
"Access-Control-Allow-Origin": "*",
}
```
This is intentional for a public blog API.
### HTTP Endpoints
| Route | Method | Auth | Description |
| --------------- | ------ | ---- | -------------------------------- |
| `/rss.xml` | GET | No | RSS feed (descriptions) |
| `/rss-full.xml` | GET | No | Full RSS feed (content for LLMs) |
| `/sitemap.xml` | GET | No | XML sitemap for SEO |
| `/api/posts` | GET | No | JSON post list |
| `/api/post` | GET | No | Single post JSON/markdown |
| `/meta/post` | GET | No | Open Graph HTML for crawlers |
## 3. Edge Function Security
### Bot Detection (botMeta.ts)
The edge function detects social media crawlers and serves Open Graph metadata:
```typescript
// Bot user agent detection
const BOTS = [
"facebookexternalhit",
"twitterbot",
// ... more bots
];
```
**Security considerations:**
- User agent can be spoofed, but this only affects OG metadata delivery
- Fallback to SPA for non-bots is secure
- No sensitive data exposed to bots
## 4. Client-Side Security
### Markdown Rendering
Uses `react-markdown` with controlled components:
- External links open with `rel="noopener noreferrer"`
- Images use lazy loading
- No raw HTML injection (markdown only)
### Copy to Clipboard
The CopyPageDropdown component uses `navigator.clipboard.writeText()` which requires user interaction and is secure.
## 5. Build-Time Security
### Environment Variables
| Variable | Purpose | Required |
| ----------------- | --------------------- | -------- |
| `VITE_CONVEX_URL` | Convex deployment URL | Yes |
| `CONVEX_URL` | Fallback Convex URL | No |
| `SITE_URL` | Canonical site URL | No |
### Sync Script
The `sync-posts.ts` script:
- Runs at build time only
- Reads markdown files from `content/blog/`
- Validates frontmatter before syncing
- Uses ConvexHttpClient with environment URL
## 6. Content Security
### Frontmatter Validation
Posts require valid frontmatter:
```typescript
// Required fields
if (!frontmatter.title || !frontmatter.date || !frontmatter.slug) {
console.warn(`Skipping ${filePath}: missing required frontmatter fields`);
return null;
}
```
### Published Flag
Only posts with `published: true` are returned by queries:
```typescript
const post = await ctx.db
.query("posts")
.withIndex("by_slug", (q) => q.eq("slug", args.slug))
.first();
if (!post || !post.published) {
return null;
}
```
## 7. Security Checklist
### Before Deploying
- [ ] Verify `VITE_CONVEX_URL` is set correctly
- [ ] Check no sensitive data in markdown files
- [ ] Review any new HTTP endpoints for proper escaping
- [ ] Ensure all external links use `noopener noreferrer`
### Convex Functions
- [ ] All queries use `.withIndex()` instead of `.filter()`
- [ ] Return validators defined for all functions
- [ ] No sensitive data in return values
### HTTP Endpoints
- [ ] HTML output uses `escapeHtml()`
- [ ] XML output uses `escapeXml()` or CDATA
- [ ] Proper Content-Type headers set
- [ ] Cache-Control headers appropriate
## 8. Known Design Decisions
### Intentionally Public
- All blog content is public by design
- No user authentication required
- API endpoints are open for LLM/agent access
- RSS feeds include full content
### Rate Limiting
- No rate limiting on view count increments
- Convex has built-in rate limiting at infrastructure level
- Consider adding application-level limits if abuse occurs
## 9. Monitoring
### Convex Dashboard
Monitor for:
- Unusual mutation activity on `syncPostsPublic`
- High query volumes on API endpoints
- Error rates on HTTP endpoints
### Netlify Analytics
Monitor for:
- Edge function errors
- Unusual traffic patterns
- Bot traffic volume
## 10. Resources
- [Convex Security Best Practices](https://docs.convex.dev/understanding/best-practices)
- [React Security Guidelines](https://react.dev/learn/security)
- [Netlify Edge Functions](https://docs.netlify.com/edge-functions/overview/)
- [React Server Components CVE](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components)