mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
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.
287 lines
7.8 KiB
Plaintext
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, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
// XML escaping for RSS feeds
|
|
function escapeXml(text: string): string {
|
|
return text
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
```
|
|
|
|
### 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)
|