SE
security-review
Security checklist and best practices for the personal blog project
Install
mkdir -p .claude/skills/security-review-augustsnow1127 && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/15607" && unzip -o skill.zip -d .claude/skills/security-review-augustsnow1127 && rm skill.zipInstalls to .claude/skills/security-review-augustsnow1127
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Security checklist and best practices for the personal blog project67 charsno explicit “when” trigger
About this skill
Security Review Checklist
This skill provides a comprehensive security review checklist for the Next.js + Notion blog.
Environment Variables Security
✅ Required Checks
- No hardcoded secrets in source code
- All API keys in
.env.localfile -
.env.localis in.gitignore - Environment variables validated at runtime
- No secrets in client-side code
Implementation
// lib/env.ts - Environment variable validation
export function validateEnv() {
const required = ['NOTION_API_KEY', 'NOTION_DATABASE_ID'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
}
// Call in server-side code only
validateEnv();
❌ Common Mistakes
// ❌ NEVER do this
const API_KEY = 'ntn_abc123...'; // Hardcoded secret
// ❌ NEVER expose in client components
'use client';
const key = process.env.NOTION_API_KEY; // Exposed to browser!
// ✅ CORRECT - Server-side only
// app/api/posts/route.ts
export async function GET() {
const key = process.env.NOTION_API_KEY; // Safe - server-side
// ...
}
API Security
Notion API Best Practices
- Rate limiting - Handle 429 responses
- Error handling - Don't expose internal errors
- Input validation - Validate all user inputs
- CORS configuration - Restrict origins if needed
// lib/notion.ts - Secure API wrapper
export async function getPosts(): Promise<NotionPost[]> {
try {
const response = await notion.databases.query({
database_id: process.env.NOTION_DATABASE_ID!,
filter: {
property: 'Published',
checkbox: { equals: true },
},
});
return response.results.map(mapNotionToPost);
} catch (error) {
// ✅ Log error server-side
console.error('Notion API error:', error);
// ❌ Don't expose internal error details
// throw error;
// ✅ Return safe fallback
return [];
}
}
Rate Limiting
// lib/rateLimit.ts
const RATE_LIMIT = 3; // requests per second
const cache = new Map<string, number[]>();
export function rateLimit(identifier: string): boolean {
const now = Date.now();
const timestamps = cache.get(identifier) || [];
// Remove timestamps older than 1 second
const recent = timestamps.filter(t => now - t < 1000);
if (recent.length >= RATE_LIMIT) {
return false; // Rate limit exceeded
}
recent.push(now);
cache.set(identifier, recent);
return true;
}
XSS Prevention
Content Sanitization
- Sanitize user input (if accepting comments/forms)
- Escape HTML in dynamic content
- Use React's built-in escaping (automatic)
- Validate Notion content before rendering
// components/NotionRenderer.tsx
export function NotionRenderer({ blocks }: { blocks: NotionBlock[] }) {
return blocks.map(block => {
switch (block.type) {
case 'paragraph':
// ✅ React automatically escapes text
return <p>{block.paragraph.rich_text[0]?.plain_text}</p>;
case 'code':
// ✅ Use proper code highlighting library
return <SyntaxHighlighter>{block.code.rich_text[0]?.plain_text}</SyntaxHighlighter>;
default:
return null;
}
});
}
❌ Dangerous Patterns
// ❌ NEVER use dangerouslySetInnerHTML with unsanitized content
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// ✅ If you must use it, sanitize first
import DOMPurify from 'isomorphic-dompurify';
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(content)
}} />
CSRF Protection
Next.js provides built-in CSRF protection for Server Actions.
// app/actions.ts
'use server';
export async function submitForm(formData: FormData) {
// ✅ Automatically protected by Next.js
const email = formData.get('email');
// Process form...
}
SQL Injection Prevention
Not applicable for this project (using Notion API), but if adding a database:
// ✅ Use parameterized queries
const result = await db.query(
'SELECT * FROM posts WHERE slug = $1',
[slug]
);
// ❌ NEVER concatenate user input
const result = await db.query(
`SELECT * FROM posts WHERE slug = '${slug}'` // VULNERABLE!
);
Authentication & Authorization
Currently not implemented, but if adding:
Checklist
- Use established auth library (NextAuth.js, Clerk, etc.)
- Implement HTTPS only
- Use secure session cookies
- Implement proper password hashing (bcrypt, Argon2)
- Add rate limiting to login endpoints
- Implement CSRF tokens
- Use secure password reset flow
Content Security Policy (CSP)
// next.config.ts
const nextConfig = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self' https://api.notion.com",
].join('; '),
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
],
},
];
},
};
Dependency Security
Regular Audits
# Check for vulnerabilities
npm audit
# Fix automatically
npm audit fix
# Check for outdated packages
npm outdated
Dependabot Configuration
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
Vercel Deployment Security
Environment Variables
- Set environment variables in Vercel dashboard
- Use different keys for preview/production
- Enable "Encrypted" option for sensitive vars
Security Headers
- Enable HTTPS only
- Configure security headers in
next.config.ts - Set up custom domain with SSL
Notion Integration Security
API Key Management
- Use integration token (not personal token)
- Limit integration permissions to minimum required
- Rotate keys periodically
- Monitor API usage
Data Validation
// lib/notion.ts - Validate Notion data
function mapNotionToPost(page: any): NotionPost {
// ✅ Validate required fields
if (!page.properties.Title?.title?.[0]?.plain_text) {
throw new Error('Invalid post: missing title');
}
if (!page.properties.Slug?.rich_text?.[0]?.plain_text) {
throw new Error('Invalid post: missing slug');
}
return {
id: page.id,
title: page.properties.Title.title[0].plain_text,
slug: page.properties.Slug.rich_text[0].plain_text,
// ... other fields
};
}
Security Review Workflow
Before Every Deployment
- Run
npm audit - Check
.env.localnot committed - Review new dependencies
- Test error handling
- Verify environment variables in Vercel
- Check security headers
- Review Notion API permissions
Monthly Security Tasks
- Update dependencies
- Rotate API keys
- Review access logs
- Check for new vulnerabilities
- Update security policies
Incident Response Plan
If a security issue is discovered:
- Assess Impact - Determine scope and severity
- Contain - Rotate compromised keys immediately
- Investigate - Review logs and access patterns
- Fix - Deploy security patch
- Notify - Inform affected users if necessary
- Document - Record incident and response