opensync / rules
waynesutton/opensync/.cursor/rules/sec-check.mdc
This document covers security patterns specific to the markdown-blog application, a Convex-powered blog with no authentication. Status: NOT AFFECTED This app does NOT use React Server Components and is NOT affected by: These vulnerabilities affect apps using: This app uses standard React 18.2.0 client-side rendering with Vite bundler. For the latest information, see: All queries in this app are intentionally public for blog content: The syncPostsPublic mutation allows syncing posts without authentication. This is intentional for build-time deployment but has…
# 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)
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
No one has posted yet. Be the first.

