sync: malware page, security alerts, review diagnostics, skill detail improvements

- Add malware security advisory page and SecurityAlertBanner component
- Add review diagnostics API and reviewed stats page
- Improve skill detail page with better scoring display and metadata
- Update review API endpoints with enhanced filtering and stats
- Add skill file serving improvements and cache enhancements
- Remove legacy curation scripts (moved to internal tooling)
- Update CLI search with score filtering support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 16:08:12 +02:00
parent 3e22d2e238
commit 16ee9e3beb
33 changed files with 1231 additions and 3404 deletions

View File

@@ -1,5 +1,5 @@
import { type NextRequest, NextResponse } from 'next/server';
import { createDb, skillReviewQueries } from '@skillhub/db';
import { createDb, skillQueries, skillReviewQueries } from '@skillhub/db';
import { requireAdmin } from '@/lib/admin-auth';
import { withRateLimit, createRateLimitResponse, createRateLimitHeaders } from '@/lib/rate-limit';
@@ -24,6 +24,7 @@ interface ReviewItem {
set_verified?: boolean;
review_version?: number;
reviewer?: string;
recommendation?: 'flag-malicious' | null;
}
function validateReviews(body: unknown): { reviews: ReviewItem[] } | { error: string } {
@@ -74,6 +75,12 @@ function validateReviews(body: unknown): { reviews: ReviewItem[] } | { error: st
return { error: `reviews[${i}].reviewer must be a non-empty string (max 50 chars)` };
}
}
// Validate recommendation
if (item.recommendation !== undefined && item.recommendation !== null) {
if (item.recommendation !== 'flag-malicious') {
return { error: `reviews[${i}].recommendation must be 'flag-malicious' or null` };
}
}
}
return { reviews: reviews as ReviewItem[] };
@@ -134,6 +141,7 @@ export async function POST(request: NextRequest) {
i18nPriority: r.i18n_priority,
contentHashAtReview: r.content_hash_at_review,
reviewVersion: r.review_version,
recommendation: r.recommendation ?? undefined,
}));
await skillReviewQueries.createBatch(db, dbReviews);
@@ -147,10 +155,20 @@ export async function POST(request: NextRequest) {
await skillReviewQueries.updateSkillReviewStatus(db, r.skill_id, newStatus, r.ai_score, new Date());
}
// Flag malicious skills
let flaggedCount = 0;
for (const r of reviews) {
if (r.recommendation === 'flag-malicious') {
await skillQueries.flagMalicious(db, r.skill_id);
flaggedCount++;
}
}
return NextResponse.json(
{
submitted: reviews.length,
verified: verifiedCount,
flagged: flaggedCount,
},
{
headers: createRateLimitHeaders(rateLimitResult),