بنقرة واحدة
security-review
Security checklist and best practices for the personal blog project
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Security checklist and best practices for the personal blog project
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
TypeScript and JavaScript coding standards for the personal blog project
Next.js 16 + React 19 + Tailwind CSS best practices and patterns for the personal blog project
Test-driven development methodology and workflow for the personal blog project
| name | security-review |
| description | Security checklist and best practices for the personal blog project |
This skill provides a comprehensive security review checklist for the Next.js + Notion blog.
.env.local file.env.local is in .gitignore// 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();
// ❌ 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
// ...
}
// 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 [];
}
}
// 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;
}
// 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;
}
});
}
// ❌ 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)
}} />
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...
}
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!
);
Currently not implemented, but if adding:
// 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',
},
],
},
];
},
};
# Check for vulnerabilities
npm audit
# Fix automatically
npm audit fix
# Check for outdated packages
npm outdated
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
next.config.ts// 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
};
}
npm audit.env.local not committedIf a security issue is discovered: