From f53bd0bcec0f073b70d434071ec4101eb0f21bd1 Mon Sep 17 00:00:00 2001 From: Subha Sankar Sahu Date: Tue, 22 Sep 2026 09:28:00 +0530 Subject: [PATCH] feat: feature improvements and UI/AI upgrade --- README.md | 10 +- client/index.html | 2 + client/src/App.tsx | 25 +- client/src/components/AIProviderStatus.tsx | 10 +- client/src/components/ai-security-summary.tsx | 342 ++++++++++++++++++ client/src/components/app-sidebar.tsx | 85 +++-- client/src/components/landing/faq-section.tsx | 133 +++++++ client/src/components/landing/features.tsx | 111 ++++++ client/src/components/landing/final-cta.tsx | 38 ++ client/src/components/landing/footer.tsx | 72 ++++ client/src/components/landing/hero.tsx | 258 +++++++++++++ .../src/components/landing/how-it-works.tsx | 80 ++++ client/src/components/landing/navbar.tsx | 138 +++++++ .../landing/pipeline-visualization.tsx | 129 +++++++ .../components/landing/pricing-section.tsx | 166 +++++++++ .../components/landing/problem-solution.tsx | 72 ++++ .../components/landing/product-showcase.tsx | 231 ++++++++++++ .../components/landing/security-section.tsx | 77 ++++ client/src/components/landing/tech-badges.tsx | 37 ++ client/src/components/onboarding-modal.tsx | 10 +- client/src/components/review-comment.tsx | 35 +- client/src/components/taint-graph-viewer.tsx | 2 +- client/src/components/theme-toggle.tsx | 27 +- client/src/hooks/use-theme.tsx | 27 +- client/src/index.css | 66 ++-- client/src/lib/queryClient.ts | 22 +- client/src/pages/auth-page.tsx | 39 +- client/src/pages/dashboard.tsx | 169 ++++++++- client/src/pages/landing-page.tsx | 61 ++++ client/src/pages/review-detail.tsx | 13 +- client/src/pages/settings.tsx | 2 +- package.json | 2 +- server/ai/provider.ts | 114 +++++- server/auth.ts | 48 ++- server/csrf.ts | 1 + server/middleware/request-logger.ts | 2 +- server/openai.ts | 192 +++++++++- server/routes.ts | 127 ++++++- server/routes/ai-status.ts | 19 +- shared/schema.ts | 20 + 40 files changed, 2839 insertions(+), 175 deletions(-) create mode 100644 client/src/components/ai-security-summary.tsx create mode 100644 client/src/components/landing/faq-section.tsx create mode 100644 client/src/components/landing/features.tsx create mode 100644 client/src/components/landing/final-cta.tsx create mode 100644 client/src/components/landing/footer.tsx create mode 100644 client/src/components/landing/hero.tsx create mode 100644 client/src/components/landing/how-it-works.tsx create mode 100644 client/src/components/landing/navbar.tsx create mode 100644 client/src/components/landing/pipeline-visualization.tsx create mode 100644 client/src/components/landing/pricing-section.tsx create mode 100644 client/src/components/landing/problem-solution.tsx create mode 100644 client/src/components/landing/product-showcase.tsx create mode 100644 client/src/components/landing/security-section.tsx create mode 100644 client/src/components/landing/tech-badges.tsx create mode 100644 client/src/pages/landing-page.tsx diff --git a/README.md b/README.md index 0c68862..2c55e68 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# CodeGuard +# CodeSift AI -Automated security and code-quality review platform for pull/merge requests, with AI analysis, optional AI remediation, custom policy enforcement, and developer-focused triage UI. +> **AI-Powered Code Security for Modern Teams** -## What CodeGuard Does +CodeSift AI is an autonomous DevSecOps SaaS platform that helps developers detect security vulnerabilities, code-quality issues, and risky changes in GitHub pull requests, understand the risks, and safely fix them. -CodeGuard connects repositories, receives webhook events, analyzes code changes, stores findings, and surfaces actionable review output in a dashboard. +## What CodeSift AI Does + +CodeSift AI connects repositories, receives webhook events, analyzes code changes, stores findings, and surfaces actionable review output in a dashboard. Core outcomes: diff --git a/client/index.html b/client/index.html index 5ac9c7f..10503cb 100644 --- a/client/index.html +++ b/client/index.html @@ -3,6 +3,8 @@ + CodeSift AI — AI-Powered Code Security + diff --git a/client/src/App.tsx b/client/src/App.tsx index 530b22a..ccff7db 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -31,12 +31,29 @@ import AdminRequests from "@/pages/admin/requests"; import AdminSystem from "@/pages/admin/system"; import AdminAuditLog from "@/pages/admin/audit-log"; import AdminExport from "@/pages/admin/export"; -import { AuthProvider } from "@/hooks/use-auth"; +import LandingPage from "@/pages/landing-page"; +import { AuthProvider, useAuth } from "@/hooks/use-auth"; import { ThemeProvider } from "@/hooks/use-theme"; import { ProtectedRoute, AdminProtectedRoute } from "@/lib/protected-route"; import { withLayout } from "@/components/layout"; import { SocketManager } from "@/components/SocketManager"; +function HomeRoute() { + const { user, isLoading } = useAuth(); + if (isLoading) { + return ( +
+
+
+ ); + } + if (!user) { + return ; + } + const DashboardWithLayout = withLayout(Dashboard); + return ; +} + function App() { return ( @@ -48,8 +65,12 @@ function App() { + {/* Public Landing & Home Route */} + + + {/* Protected Routes with Sidebar */} - + diff --git a/client/src/components/AIProviderStatus.tsx b/client/src/components/AIProviderStatus.tsx index 26ba4c9..0cb7698 100644 --- a/client/src/components/AIProviderStatus.tsx +++ b/client/src/components/AIProviderStatus.tsx @@ -33,7 +33,7 @@ interface AIStatus { successes: number; failures: number; }; - activeProvider: "nim" | "openai" | "none"; + activeProvider: "nim" | "openai" | "groq" | "none"; lastFailureReason: string | null; forceFallback: boolean; } @@ -120,9 +120,13 @@ export default function AIProviderStatus() { variant="outline" className={status.activeProvider === 'nim' ? "bg-green-500/10 text-green-500 border-green-500/20" - : "bg-blue-500/10 text-blue-500 border-blue-500/20"} + : status.activeProvider === 'groq' + ? "bg-emerald-500/10 text-emerald-500 border-emerald-500/20" + : status.activeProvider === 'openai' + ? "bg-blue-500/10 text-blue-500 border-blue-500/20" + : "bg-muted text-muted-foreground"} > - {status.activeProvider === 'nim' ? 'NVIDIA NIM Active' : status.activeProvider === 'openai' ? 'OpenAI Fallback' : 'Idle'} + {status.activeProvider === 'nim' ? 'NVIDIA NIM Active' : status.activeProvider === 'groq' ? 'Groq Active (Primary)' : status.activeProvider === 'openai' ? 'OpenAI Fallback' : 'Idle'} )} + + + + ); + } + + // State: Loading / Mutating skeleton + if (isLoading || generateMutation.isPending) { + return ( + + +
+
+ + Generating AI Security Summary... +
+ +
+ + Synthesizing review findings, policy violations, and taint paths with AI... + +
+ +
+ + +
+ + +
+
+ ); + } + + // State: Error state with retry + if (isError || generateMutation.isError) { + return ( + + +
+
+ + AI Security Summary Unavailable +
+ +
+ + {(error as any)?.message || (generateMutation.error as any)?.message || "Failed to generate AI security summary."} + +
+
+ ); + } + + if (!summary) return null; + + // Styling based on risk level and score + const isHigh = summary.riskLevel === "HIGH"; + const isMedium = summary.riskLevel === "MEDIUM"; + const isLow = summary.riskLevel === "LOW"; + + const riskBadgeClass = isHigh + ? "bg-red-500/10 text-red-600 border-red-500/30 dark:text-red-400" + : isMedium + ? "bg-amber-500/10 text-amber-600 border-amber-500/30 dark:text-amber-400" + : "bg-emerald-500/10 text-emerald-600 border-emerald-500/30 dark:text-emerald-400"; + + const scoreColor = summary.securityScore >= 80 + ? "text-emerald-500" + : summary.securityScore >= 50 + ? "text-amber-500" + : "text-red-500"; + + const scoreProgressColor = summary.securityScore >= 80 + ? "bg-emerald-500" + : summary.securityScore >= 50 + ? "bg-amber-500" + : "bg-red-500"; + + return ( + + +
+
+
+ +
+
+ + AI Security Summary + + Groq / AI Powered + + + + Comprehensive security posture synthesis for this Pull Request + +
+
+ +
+ +
+
+
+ + + {/* Top Metric Bar: Risk Level & Security Score */} +
+ {/* Risk Level */} +
+
+ + Risk Level + +
+ {isHigh && } + {isMedium && } + {isLow && } + + {summary.riskLevel} + +
+
+ + {summary.riskLevel} RISK + +
+ + {/* Security Score */} +
+
+ + Security Score + + + {summary.securityScore}/100 + +
+ {/* Progress Bar */} +
+
+
+
+ 0 (Critical) + 100 (Safe) +
+
+
+ + {/* Key Risks */} + {summary.keyRisks && summary.keyRisks.length > 0 && ( +
+
+ + Key Risks +
+
+
    + {summary.keyRisks.map((risk, idx) => ( +
  • + + {risk} +
  • + ))} +
+
+
+ )} + + {/* Potential Impact */} + {summary.potentialImpact && ( +
+
+ + Potential Impact +
+
+ {summary.potentialImpact} +
+
+ )} + + {/* Recommended Actions */} + {summary.recommendedActions && summary.recommendedActions.length > 0 && ( +
+
+ + Recommended Actions +
+
+
    + {summary.recommendedActions.map((action, idx) => ( +
  • + + {action} +
  • + ))} +
+
+
+ )} + + {/* Why this matters */} + {summary.whyThisMatters && ( +
+
+ + Why This Matters +
+
+ "{summary.whyThisMatters}" +
+
+ )} + + + ); +} diff --git a/client/src/components/app-sidebar.tsx b/client/src/components/app-sidebar.tsx index 6641881..6cc0de6 100644 --- a/client/src/components/app-sidebar.tsx +++ b/client/src/components/app-sidebar.tsx @@ -33,6 +33,7 @@ import { import { Badge } from "@/components/ui/badge"; import { useAuth } from "@/hooks/use-auth"; import { PremiumAvatar } from "@/components/ui/premium-avatar"; +import { queryClient } from "@/lib/queryClient"; import { Button } from "@/components/ui/button"; import { apiRequest } from "@/lib/queryClient"; import { clearCsrfToken, getCsrfToken } from "@/lib/csrf"; @@ -129,13 +130,51 @@ export function AppSidebar() { const handleLogout = async () => { try { - await getCsrfToken(); - await apiRequest("POST", "/api/logout"); - } catch { - // still clear client state + // 1. Fetch fresh CSRF token so POST works on both old and new backend + let token = await getCsrfToken().catch(() => null); + if (!token) { + try { + const csrfRes = await fetch("/api/csrf", { credentials: "include" }); + if (csrfRes.ok) { + const data = (await csrfRes.json()) as { csrfToken?: string }; + token = data.csrfToken ?? null; + } + } catch {} + } + + // 2. Call POST /api/logout with CSRF token and credentials + await fetch("/api/logout", { + method: "POST", + credentials: "include", + headers: { + ...(token ? { "x-csrf-token": token } : {}), + "Content-Type": "application/json", + }, + }); + + // 3. Send fallback GET /api/logout to ensure session destruction across all handlers + await fetch("/api/logout", { + method: "GET", + credentials: "include", + }); + } catch (err) { + console.error("Logout request error:", err); } finally { + // 4. Invalidate all CSRF and auth cache clearCsrfToken(); - window.location.href = "/auth"; + queryClient.setQueryData(["/api/user"], null); + queryClient.removeQueries({ queryKey: ["/api/user"] }); + queryClient.clear(); + + // 5. Clear client-accessible cookies and local/session storage + try { + document.cookie = "codeguard.sid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + localStorage.clear(); + sessionStorage.clear(); + } catch {} + + // 6. Hard redirect to landing page so the browser unloads the authenticated view + window.location.replace("/landing"); } }; @@ -146,14 +185,14 @@ export function AppSidebar() {
CodeGuard logo
- CodeGuard - signal over noise + CodeSift AI + AI-Powered DevSecOps
@@ -216,34 +255,6 @@ export function AppSidebar() { - - - - Changelog - - - - - - - Developer - - - - - - - Terms & Conditions - - - - - - - Privacy Policy - - - @@ -275,7 +286,7 @@ export function AppSidebar() {
v1.0.0 - CodeGuard + CodeSift AI
diff --git a/client/src/components/landing/faq-section.tsx b/client/src/components/landing/faq-section.tsx new file mode 100644 index 0000000..5550e29 --- /dev/null +++ b/client/src/components/landing/faq-section.tsx @@ -0,0 +1,133 @@ +import React from "react"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { motion } from "framer-motion"; + +export function FAQSection() { + const faqs = [ + { + q: "What is CodeSift AI?", + a: "CodeSift AI is a DevSecOps platform built for developers. It automatically analyzes GitHub pull requests, flags vulnerabilities, identifies risky architectural changes, explains why issues exist, and suggests line-specific remediation.", + }, + { + q: "How does CodeSift AI analyze pull requests?", + a: "When a pull request is opened or updated, CodeSift AI inspects the changed diff using AST parsing, cross-function taint analysis, and semantic AI models. It identifies data flows that reach sensitive sinks and checks repository-specific security policies.", + }, + { + q: "Does it work with GitHub?", + a: "Yes. CodeSift AI is natively integrated with GitHub using standard OAuth and webhooks. It works directly in your pull requests without requiring changes to your git workflow or CI configuration.", + }, + { + q: "Can it explain security findings?", + a: "Yes. Instead of cryptic rule codes, every finding includes an AI explanation detailing 'Why this is a risk' and 'How the fix works', helping developers understand the vulnerability's impact in plain technical terms.", + }, + { + q: "Can it help fix vulnerabilities?", + a: "Yes. CodeSift AI provides assisted remediation. It drafts precise code fixes directly addressing the finding. You can review the changes and apply them with safety guards that prevent editing sensitive system files.", + }, + ]; + + return ( +
+
+ + {/* Section Header */} +
+
+ QUESTIONS +
+

+ Frequently asked questions +

+
+ + {/* 2-Column Accordion Layout Utilizing Left & Right Space */} +
+ {/* Left Column */} +
+ + {faqs.slice(0, 3).map((faq, idx) => ( + + + + {faq.q} + + + {faq.a} + + + + ))} + +
+ + {/* Right Column */} +
+ + {faqs.slice(3).map((faq, idx) => ( + + + + {faq.q} + + + {faq.a} + + + + ))} + + + {/* Help & Contact Card */} + +
+
Have a specific security question?
+
Our DevSecOps engineering team is here to assist.
+
+ + Contact team → + +
+
+
+
+
+
+ ); +} diff --git a/client/src/components/landing/features.tsx b/client/src/components/landing/features.tsx new file mode 100644 index 0000000..02499b0 --- /dev/null +++ b/client/src/components/landing/features.tsx @@ -0,0 +1,111 @@ +import React from "react"; +import { + Code2, + ShieldAlert, + GitBranch, + Sliders, + Wrench, + BarChart3, +} from "lucide-react"; +import { motion } from "framer-motion"; + +export function FeaturesSection() { + const items = [ + { + category: "AI CODE REVIEW", + title: "Automated Pull Request Review", + description: + "Analyze changed code and identify security vulnerabilities and code-quality risks in seconds.", + icon: Code2, + }, + { + category: "SECURITY ANALYSIS", + title: "Vulnerability Pattern Detection", + description: + "Detect SQL injection, SSRF, hardcoded credentials, and dangerous execution sinks with high precision.", + icon: ShieldAlert, + }, + { + category: "TAINT ANALYSIS", + title: "Cross-Function Taint Tracking", + description: + "Trace untrusted, user-controlled inputs as they flow across boundaries into sensitive database calls.", + icon: GitBranch, + }, + { + category: "POLICY ENFORCEMENT", + title: "Custom Security Rules", + description: + "Apply repository-specific compliance requirements, restricted dependencies, and branch merge gates.", + icon: Sliders, + }, + { + category: "AI REMEDIATION", + title: "Assisted Patch Generation", + description: + "Understand exactly why each finding is a risk, review generated fixes, and remediate with confidence.", + icon: Wrench, + }, + { + category: "SECURITY ANALYTICS", + title: "Repository Posture Metrics", + description: + "Track aggregate posture scores, open risk counts, and resolution velocity across team reviews.", + icon: BarChart3, + }, + ]; + + return ( +
+
+ + {/* Section Header */} +
+
+ CAPABILITIES +
+

+ Engineered for security depth without review latency. +

+
+ + {/* Clean Editorial Grid with Staggered Framer Motion */} +
+ {items.map((item, idx) => { + const Icon = item.icon; + return ( + +
+ + + {item.category} + +
+

+ {item.title} +

+

+ {item.description} +

+
+ ); + })} +
+
+
+
+ ); +} diff --git a/client/src/components/landing/final-cta.tsx b/client/src/components/landing/final-cta.tsx new file mode 100644 index 0000000..98b84d2 --- /dev/null +++ b/client/src/components/landing/final-cta.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { Link } from "wouter"; +import { motion } from "framer-motion"; + +export function FinalCTASection() { + return ( +
+
+ +
+ READY TO DEPLOY +
+

+ Make every pull request easier to trust. +

+

+ Add an autonomous AI-powered security layer to your development workflow. No configs required. +

+
+ + Get started now + +
+
+
+
+ ); +} diff --git a/client/src/components/landing/footer.tsx b/client/src/components/landing/footer.tsx new file mode 100644 index 0000000..e5b1e3a --- /dev/null +++ b/client/src/components/landing/footer.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import { Link } from "wouter"; +import { Shield } from "lucide-react"; +import { motion } from "framer-motion"; + +export function LandingFooter() { + return ( + +
+
+ {/* Brand & Tagline */} +
+ +
+ +
+ + CodeSift AI + + +

+ AI-powered security for modern software development. +

+
+ + {/* Links */} + +
+ + {/* Bottom divider & copyright */} +
+

© {new Date().getFullYear()} CodeSift AI. All rights reserved.

+
+ + Privacy Policy + + + Terms of Service + +
+
+
+
+ ); +} diff --git a/client/src/components/landing/hero.tsx b/client/src/components/landing/hero.tsx new file mode 100644 index 0000000..71b8199 --- /dev/null +++ b/client/src/components/landing/hero.tsx @@ -0,0 +1,258 @@ +import React from "react"; +import { Link } from "wouter"; +import { motion } from "framer-motion"; + +export function HeroSection() { + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + delayChildren: 0.05, + }, + }, + }; + + const itemVariants = { + hidden: { opacity: 0, y: 22 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.55, + ease: [0.22, 1, 0.36, 1], + }, + }, + }; + + return ( +
+
+
+ {/* Left Column: Copy + CTAs + Trust Metrics (Uses Left Space) */} + + {/* Eyebrow */} + + + AI-POWERED CODE SECURITY + + + {/* Main Heading */} + + Ship secure code with less friction. + + + {/* Supporting Text */} + + CodeSift AI reviews your pull requests for security vulnerabilities, + risky changes, and code-quality issues — then helps your team + understand and fix them directly in GitHub. + + + {/* Action Buttons */} + + + Get started + + + View demo + + + + {/* Live Security Metrics (Fills and Balances Left Column) */} + +
+
< 3s
+
Scan latency
+
+
+
OWASP
+
A01-A10 coverage
+
+
+
0%
+
Code retention
+
+
+
+ + {/* Right Column: Live Interactive PR Preview Window (Uses Right Space) */} + +
+ {/* Window Header */} +
+
+ + CodeSift AI + / + my-project + / + Pull Request #42 +
+
+ branch: feature/auth-v2 + + ● live + +
+
+ + {/* Score & Metric Bar */} +
+
+
+ Security score +
+
+ 82/100 +
+
+ + {/* Severity Breakdown Badges */} +
+ + + 2 Critical + + + + 4 High + + + + 7 Medium + +
+
+ + {/* Two-Column Tool Content: Findings & AI Summary */} +
+ {/* Findings Column */} +
+
+ + Findings + + 1 of 13 showing +
+ +
+
+ + HIGH + + + src/database/users.ts:42 + +
+ +
+

+ SQL Injection +

+

+ User-controlled input reaches a database query without parameterization. +

+
+ + {/* Diff Snippet */} +
+
{"- const query = `SELECT * FROM users WHERE id = ${userId}`;"}
+
{"+ const query = 'SELECT * FROM users WHERE id = $1'; [userId]"}
+
+ +
+ + [View finding] + + CWE-89 • OWASP A03 +
+
+
+ + {/* AI Summary Column */} +
+
+ + AI Summary + + Ready +
+ +
+

+ This pull request introduces several security risks that should be addressed before merging. +

+

+ Primary concerns include raw string interpolation in query builders. +

+
+ + + + {/* Policy Status Box */} +
+
+ Policy Check +
+
+ OWASP Top 10 + High Risk +
+
+ Sensitive Files + Protected +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/client/src/components/landing/how-it-works.tsx b/client/src/components/landing/how-it-works.tsx new file mode 100644 index 0000000..6939d14 --- /dev/null +++ b/client/src/components/landing/how-it-works.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import { motion } from "framer-motion"; + +export function HowItWorksSection() { + const steps = [ + { + number: "01", + title: "Connect GitHub", + description: "Grant repository access via GitHub OAuth with zero custom configuration files.", + }, + { + number: "02", + title: "Open a pull request", + description: "Push branches and open pull requests normally inside your current workflow.", + }, + { + number: "03", + title: "CodeSift AI analyzes the changes", + description: "Automated engine maps taint flows, checks policy rules, and evaluates risk.", + }, + { + number: "04", + title: "Review findings", + description: "Read contextual explanations, line-level diff annotations, and security scores.", + }, + { + number: "05", + title: "Fix and merge", + description: "Review the proposed fix, apply remediation, and merge clean code with confidence.", + }, + ]; + + return ( +
+
+ + {/* Section Header */} +
+
+ PROCESS +
+

+ How it works +

+
+ + {/* Numbered Timeline Grid with Staggered Framer Motion */} +
+ {steps.map((step, idx) => ( + +
+ {step.number} +
+

+ {step.title} +

+

+ {step.description} +

+
+ ))} +
+
+
+
+ ); +} diff --git a/client/src/components/landing/navbar.tsx b/client/src/components/landing/navbar.tsx new file mode 100644 index 0000000..c893623 --- /dev/null +++ b/client/src/components/landing/navbar.tsx @@ -0,0 +1,138 @@ +import React, { useState, useEffect } from "react"; +import { Link } from "wouter"; +import { Menu, X, Shield } from "lucide-react"; +import { motion } from "framer-motion"; +import { ThemeToggle } from "@/components/theme-toggle"; + +export function LandingNavbar() { + const [scrolled, setScrolled] = useState(false); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + + useEffect(() => { + const handleScroll = () => { + setScrolled(window.scrollY > 10); + }; + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + return ( + +
+ {/* Left: Simple text / logo mark */} + +
+ +
+ + CodeSift AI + + + + {/* Center: Minimal Navigation */} + + + {/* Right: Theme Toggle, Sign in & Get started */} +
+ + + Sign in + + + Get started + +
+ + {/* Mobile menu toggle */} +
+ + +
+
+ + {/* Mobile dropdown */} + {mobileMenuOpen && ( + + )} +
+ ); +} diff --git a/client/src/components/landing/pipeline-visualization.tsx b/client/src/components/landing/pipeline-visualization.tsx new file mode 100644 index 0000000..c36b0b8 --- /dev/null +++ b/client/src/components/landing/pipeline-visualization.tsx @@ -0,0 +1,129 @@ +import React from "react"; +import { motion } from "framer-motion"; + +export function SecurityWorkflowSection() { + const flow = [ + { name: "GitHub PR", meta: "PR open / commit hook" }, + { name: "CodeSift AI", meta: "Automated gate check" }, + { name: "Code Analysis", meta: "AST & taint tracking" }, + { name: "Security Findings", meta: "Severity classification" }, + { name: "AI Explanation", meta: "Root cause rationale" }, + { name: "Suggested Fix", meta: "Verified code patch" }, + { name: "Developer Review", meta: "Engineer approval" }, + { name: "Merge", meta: "Protected trunk update" }, + ]; + + return ( +
+
+ + {/* Section Header */} +
+
+ DEVSECOPS PIPELINE +
+

+ Security workflow +

+

+ A deterministic developer lifecycle from pull request creation to secure merge. +

+
+ + {/* Visual Developer Workflow */} + + {/* Desktop Flow (horizontal wrapped or 8-col grid) */} +
+ {flow.map((step, idx) => ( + + + 0{idx + 1} + + + {step.name} + + + {step.meta} + + + ))} +
+ + {/* Connected arrow row for Desktop */} +
+ {flow.map((_, idx) => ( + +
+ {idx < flow.length - 1 ? "↓" : "✓"} +
+
+ ))} +
+ + {/* Mobile & Tablet Flow */} +
+ {flow.map((step, idx) => ( + +
+
+ + 0{idx + 1} + + + {step.name} + +
+ + {step.meta} + +
+ {idx < flow.length - 1 && ( +
+ ↓ +
+ )} +
+ ))} +
+ + {/* Workflow Summary Footer */} +
+
+ + Full execution completes in < 3 seconds per pull request +
+ + Zero persistent source-code storage • Ephemeral diff scanning + +
+
+
+
+
+ ); +} + +export const PipelineVisualizationSection = SecurityWorkflowSection; diff --git a/client/src/components/landing/pricing-section.tsx b/client/src/components/landing/pricing-section.tsx new file mode 100644 index 0000000..d76e1cb --- /dev/null +++ b/client/src/components/landing/pricing-section.tsx @@ -0,0 +1,166 @@ +import React from "react"; +import { Link } from "wouter"; +import { motion } from "framer-motion"; + +export function PricingSection() { + const plans = [ + { + name: "FREE", + tagline: "For students and individual developers.", + price: "$0", + period: "forever", + features: [ + "Public & personal repositories", + "Automated pull request analysis", + "Security vulnerability detection", + "Basic taint analysis", + "Community support", + ], + cta: "Get started", + isHighlighted: false, + }, + { + name: "PRO", + tagline: "For development teams.", + price: "$29", + period: "per user / month", + features: [ + "Private repositories", + "High-depth semantic AI review", + "AI remediation with 1-click fixes", + "Custom policy enforcement", + "Cross-file taint tracking", + "Priority queue processing", + ], + cta: "Get started", + isHighlighted: true, + }, + { + name: "ENTERPRISE", + tagline: "For organizations.", + price: "Custom", + period: "annual billing", + features: [ + "Self-hosted / VPC deployment", + "Custom compliance policies (SOC2, ASVS)", + "Dedicated rate limits & SLA", + "Single sign-on (SAML / SSO)", + "Audit logging & enterprise support", + ], + cta: "Contact us", + isHighlighted: false, + }, + ]; + + return ( +
+
+ + {/* Section Header */} +
+
+ PRICING +
+

+ Simple, predictable pricing. +

+

+ Transparent plans for developers and engineering teams. +

+
+ + {/* 3 Columns with Staggered Framer Motion */} +
+ {plans.map((plan, idx) => ( + +
+
+
+

+ {plan.name} +

+ {plan.isHighlighted && ( + + POPULAR + + )} +
+

+ {plan.tagline} +

+
+ +
+ + {plan.price} + + + /{plan.period} + +
+ +
+ + Included + + {plan.features.map((feat, fIdx) => ( +
+ — + {feat} +
+ ))} +
+
+ +
+ {plan.name === "ENTERPRISE" ? ( + + {plan.cta} + + ) : plan.isHighlighted ? ( + + {plan.cta} + + ) : ( + + {plan.cta} + + )} +
+
+ ))} +
+
+
+
+ ); +} diff --git a/client/src/components/landing/problem-solution.tsx b/client/src/components/landing/problem-solution.tsx new file mode 100644 index 0000000..26fcc6c --- /dev/null +++ b/client/src/components/landing/problem-solution.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import { motion } from "framer-motion"; + +export function ProblemSolutionSection() { + return ( +
+
+
+ {/* LEFT: Problem (Slide from Left) */} + +
+ THE PROBLEM +
+

+ Code review doesn’t always catch security risk. +

+

+ Manual pull request reviews prioritize business logic, code style, + and fast turnaround. Subtler security flaws—like indirect taint flows, + unparameterized queries, and leaky dependencies—frequently pass review unnoticed. +

+
+ + {/* RIGHT: Solution (Slide from Right) */} + +
+ THE SOLUTION +
+

+ CodeSift AI adds an automated security layer to your existing pull request workflow. +

+
+
+ — +

+ Continuous deep analysis:{" "} + Inspects every changed line and dependency before pull requests merge into main branches. +

+
+
+ — +

+ Deterministic rules + semantic intelligence:{" "} + Combines syntax taint analysis with contextual AI explanations to eliminate noisy alerts. +

+
+
+ — +

+ Remediation in context:{" "} + Provides line-specific fixes directly in GitHub comments with full architectural explanation. +

+
+
+
+
+
+
+ ); +} diff --git a/client/src/components/landing/product-showcase.tsx b/client/src/components/landing/product-showcase.tsx new file mode 100644 index 0000000..b1b0e5a --- /dev/null +++ b/client/src/components/landing/product-showcase.tsx @@ -0,0 +1,231 @@ +import React from "react"; +import { Wrench } from "lucide-react"; +import { motion } from "framer-motion"; + +export function ProductShowcaseSection() { + return ( +
+
+ + {/* Section Header */} +
+
+ PRODUCT INTERFACE +
+

+ Built with the fidelity of a production developer tool. +

+

+ Real pull request data, deterministic policy status, and verifiable AI patch generation. +

+
+ + {/* Real Product UI Container with Framer Motion Lift */} + + {/* Top Metadata Header */} +
+
+
+ repo: org/backend-api +
+ | +
+ pr: #128 Fix session revocation +
+
+ +
+ + Risk Level: HIGH + + + Policy: Protected + +
+
+ + {/* Stats Bar */} +
+
+
+ Security Posture +
+
+ 78/100 +
+
+ +
+
+ Critical Risks +
+
0
+
+ +
+
+ High / Medium +
+
+ 1 / 2 +
+
+ +
+
+ Policy Checks +
+
+ 4 Passed +
+
+
+ + {/* Main Showcase Layout: 2 Columns */} +
+ {/* Left Column: AI Security Summary & Policy Status */} +
+
+
+ AI Security Summary +
+
+
+ + Key Risks + +
    +
  • + • + Hardcoded authentication secret committed in source. +
  • +
  • + • + Unvalidated redirect destination in OAuth callback handler. +
  • +
+
+ +
+ + Potential Impact + +

+ Compromised token signatures allow session spoofing across + production microservices without needing valid credentials. +

+
+ +
+ + Recommended Actions + +

+ Migrate secret reference to environment variable and add + automated pre-commit entropy verification. +

+
+
+
+ + {/* Policy Status Box */} +
+
+ Policy Status +
+
+
+ OWASP Secret Detection + Failed (1 Finding) +
+
+ Sensitive File Guard (Safety Guard) + Enforced (Protected) +
+
+
+
+ + {/* Right Column: Findings & AI Fix Assistant UI */} +
+
+
+ AI Fix Interface +
+
+ server/auth.ts:18 +
+
+ + {/* Interactive Card Showing AI Explanation Before Fix */} +
+
+ + HIGH — Hardcoded Secret + + + Line 18 + +
+ + {/* Why this is a risk */} +
+ + Why this is a risk + +

+ Hardcoded authentication tokens in source files are exposed to anyone + with repository access and cannot be rotated without code deployments. +

+
+ + {/* How the fix works */} +
+ + How the fix works + +

+ Replaces static token string with process.env.SESSION_SECRET and adds an explicit check during application bootstrap. +

+
+ + {/* Patch Diff */} +
+
{"- const SECRET = \"EXAMPLE_HARDCODED_TOKEN_12345\";"}
+
{"+ const SECRET = process.env.SESSION_SECRET;"}
+
+ + {/* Action button */} +
+ + + Verified by safety-guard.ts + +
+
+
+
+
+
+
+
+ ); +} diff --git a/client/src/components/landing/security-section.tsx b/client/src/components/landing/security-section.tsx new file mode 100644 index 0000000..cbed945 --- /dev/null +++ b/client/src/components/landing/security-section.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import { ShieldCheck, Cpu, Sliders, Lock, GitBranch } from "lucide-react"; + +export function SecuritySection() { + const principles = [ + { + title: "Secure by Design", + description: + "Engineered with least privilege and ephemeral execution for all diff analyses.", + icon: ShieldCheck, + }, + { + title: "AI-Assisted Analysis", + description: + "Multi-model synthesis with rigorous structured verification to prevent hallucinations.", + icon: Cpu, + }, + { + title: "Policy Enforcement", + description: + "Deterministic rule evaluation adhering to OWASP ASVS and organization policies.", + icon: Sliders, + }, + { + title: "Protected Remediation", + description: + "Automated guardrails preventing AI from altering sensitive authentication or billing files.", + icon: Lock, + }, + { + title: "GitHub-Native Workflow", + description: + "Seamless webhooks and status checks integrated directly into developer PR discussions.", + icon: GitBranch, + }, + ]; + + return ( +
+
+
+ + Enterprise Integrity + +

+ Security is part of the workflow. +

+

+ We hold our platform to the same uncompromising security engineering standards we enforce on your repositories. +

+
+ +
+ {principles.map((item, idx) => { + const Icon = item.icon; + return ( +
+
+ +
+

+ {item.title} +

+

+ {item.description} +

+
+ ); + })} +
+
+
+ ); +} diff --git a/client/src/components/landing/tech-badges.tsx b/client/src/components/landing/tech-badges.tsx new file mode 100644 index 0000000..bbd3860 --- /dev/null +++ b/client/src/components/landing/tech-badges.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { SiGithub, SiReact, SiNodedotjs, SiPostgresql } from "react-icons/si"; +import { Cpu } from "lucide-react"; + +export function TechBadgesSection() { + const techs = [ + { name: "GitHub", icon: SiGithub, label: "GitHub-Native" }, + { name: "React", icon: SiReact, label: "React" }, + { name: "Node.js", icon: SiNodedotjs, label: "Node.js" }, + { name: "PostgreSQL", icon: SiPostgresql, label: "PostgreSQL" }, + { name: "AI", icon: Cpu, label: "AI Models" }, + ]; + + return ( +
+
+

+ Built for modern development workflows +

+
+ {techs.map((tech) => { + const Icon = tech.icon; + return ( +
+ + {tech.label} +
+ ); + })} +
+
+
+ ); +} diff --git a/client/src/components/onboarding-modal.tsx b/client/src/components/onboarding-modal.tsx index 44301c0..f5ac37d 100644 --- a/client/src/components/onboarding-modal.tsx +++ b/client/src/components/onboarding-modal.tsx @@ -8,8 +8,8 @@ import { motion, AnimatePresence } from "framer-motion"; const STEPS = [ { id: 1, - title: "Welcome to CodeGuard", - description: "Your AI-powered guardian for code quality and security. Let's get you set up in seconds.", + title: "Welcome to CodeSift AI", + description: "Your AI-powered DevSecOps platform for code quality and security. Let's get you set up in seconds.", icon: }, { @@ -21,7 +21,7 @@ const STEPS = [ { id: 3, title: "AI Code Analysis", - description: "CodeGuard scans your code for bugs, vulnerabilities, and smells as soon as you open a PR.", + description: "CodeSift AI scans your code for bugs, vulnerabilities, and smells as soon as you open a PR.", icon: }, { @@ -52,9 +52,9 @@ export function OnboardingModal() { return ( - Getting Started with CodeGuard + Getting Started with CodeSift AI - A short tutorial on how to use CodeGuard effectively. + A short tutorial on how to use CodeSift AI effectively. {safeCommentText}

{showFixButton && ( -
+
+ {/* AI Fix Explanation Section */} +
+
+
+ + Why This Is A Risk +
+

+ {type === "security" + ? `This pattern at line ${comment.line} in ${comment.path} presents a security vulnerability. Untrusted input or exposed credentials can lead to unauthorized access or system exploitation.` + : type === "bug" + ? `This code flaw at line ${comment.line} in ${comment.path} can cause unexpected runtime exceptions, state corruption, or silent failures in production.` + : `This issue at line ${comment.line} introduces technical debt and reliability risks that impact operational stability.`} +

+
+ +
+
+ + How The Fix Works +
+

+ CodeSift AI's engine analyzes the file AST, removes the unsafe pattern, and injects validated secure constructs without altering existing business logic. +

+
+
+ + {/* Existing Apply AI Fix Button and Flow */} { setIsDialogOpen(open); if (!open) { @@ -178,6 +208,7 @@ export function ReviewCommentCard({ comment, platform = "github" }: ReviewCommen handleApplyFix(); // Start the background fix process }} disabled={isFixing && !isDialogOpen} + data-testid={`btn-apply-fix-${comment.id}`} > Apply AI Fix diff --git a/client/src/components/taint-graph-viewer.tsx b/client/src/components/taint-graph-viewer.tsx index c0ae873..1548b1a 100644 --- a/client/src/components/taint-graph-viewer.tsx +++ b/client/src/components/taint-graph-viewer.tsx @@ -188,7 +188,7 @@ export function TaintGraphViewer({ reviewId }: { reviewId: string }) { // Theme-aware colors const colors = { - background: theme === "light" ? "hsl(var(--background))" : theme === "midnight" ? "#000000" : "hsl(var(--background))", + background: "hsl(var(--background))", grid: theme === "light" ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.05)", miniMapNode: theme === "light" ? "#e2e8f0" : "#1e293b", miniMapMask: theme === "light" ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.7)", diff --git a/client/src/components/theme-toggle.tsx b/client/src/components/theme-toggle.tsx index ebb8810..147d66f 100644 --- a/client/src/components/theme-toggle.tsx +++ b/client/src/components/theme-toggle.tsx @@ -1,9 +1,9 @@ -import { Moon, Sun, Sparkles } from "lucide-react"; +import { Moon, Sun } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTheme } from "@/hooks/use-theme"; import { motion, AnimatePresence } from "framer-motion"; -export function ThemeToggle() { +export function ThemeToggle({ className }: { className?: string }) { const { theme, cycleTheme } = useTheme(); return ( @@ -11,24 +11,27 @@ export function ThemeToggle() { size="icon" variant="ghost" onClick={(e) => cycleTheme(e)} - className="relative h-10 w-10 rounded-full border border-border/40 bg-background/50 hover:bg-accent/50 transition-all duration-300 overflow-hidden" + className={`relative h-9 w-9 rounded-md border border-border/60 bg-card/60 hover:bg-accent/60 text-foreground transition-colors overflow-hidden ${className || ""}`} + title={`Switch to ${theme === "dark" ? "Light" : "Dark"} mode`} data-testid="button-theme-toggle" > - + - {theme === "light" && } - {theme === "dark" && } - {theme === "midnight" && } + {theme === "dark" ? ( + + ) : ( + + )} - Cycle Theme + Toggle theme ); } diff --git a/client/src/hooks/use-theme.tsx b/client/src/hooks/use-theme.tsx index df027f2..5dfdb1e 100644 --- a/client/src/hooks/use-theme.tsx +++ b/client/src/hooks/use-theme.tsx @@ -1,6 +1,6 @@ import React, { createContext, useContext, useEffect, useState } from "react"; -type Theme = "light" | "dark" | "midnight"; +export type Theme = "light" | "dark"; interface ThemeContextType { theme: Theme; @@ -11,14 +11,15 @@ interface ThemeContextType { const ThemeContext = createContext(undefined); export function ThemeProvider({ children }: { children: React.ReactNode }) { - const [theme, setThemeState] = useState("light"); + const [theme, setThemeState] = useState("dark"); useEffect(() => { const stored = localStorage.getItem("theme") as Theme | null; - if (stored) { + if (stored && (stored === "light" || stored === "dark")) { setThemeState(stored); applyTheme(stored); - } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + } else { + // Default to dark mode to match the CodeSift AI security palette setThemeState("dark"); applyTheme("dark"); } @@ -27,8 +28,10 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) { const applyTheme = (t: Theme) => { const root = document.documentElement; root.classList.remove("light", "dark", "midnight"); - if (t !== "light") { - root.classList.add(t); + if (t === "dark") { + root.classList.add("dark"); + } else { + root.classList.add("light"); } }; @@ -39,14 +42,8 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) { }; const cycleTheme = (event?: React.MouseEvent) => { - const nextTheme: Record = { - light: "dark", - dark: "midnight", - midnight: "light", - }; - const next = nextTheme[theme]; + const next: Theme = theme === "light" ? "dark" : "light"; - // Check if the browser supports View Transitions API // @ts-ignore if (!document.startViewTransition || window.matchMedia("(prefers-reduced-motion: reduce)").matches || !event) { setTheme(next); @@ -74,8 +71,8 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) { ], }, { - duration: 500, - easing: "cubic-bezier(0.4, 0, 0.2, 1)", + duration: 350, + easing: "cubic-bezier(0.2, 0, 0, 1)", pseudoElement: "::view-transition-new(root)", } ); diff --git a/client/src/index.css b/client/src/index.css index c0882ce..7362a44 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -144,40 +144,40 @@ --opaque-button-border-intensity: 9; --elevate-1: rgba(255,255,255, .04); --elevate-2: rgba(255,255,255, .09); - --background: 20 14% 4%; - --foreground: 45 25% 91%; - --border: 20 14% 15%; - --card: 20 14% 8%; - --card-foreground: 45 25% 85%; - --card-border: 20 14% 12%; - --sidebar: 20 14% 8%; - --sidebar-foreground: 45 25% 85%; - --sidebar-border: 20 14% 15%; - --sidebar-primary: 9 75% 61%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 25 45% 20%; - --sidebar-accent-foreground: 45 25% 85%; - --sidebar-ring: 9 75% 61%; - --popover: 20 14% 4%; - --popover-foreground: 45 25% 91%; - --popover-border: 20 14% 10%; - --primary: 9 75% 61%; - --primary-foreground: 0 0% 100%; - --secondary: 30 15% 52%; - --secondary-foreground: 0 0% 100%; - --muted: 20 14% 15%; - --muted-foreground: 45 15% 46%; - --accent: 25 45% 20%; - --accent-foreground: 45 25% 85%; - --destructive: 356.3033 90.5579% 54.3137%; + --background: 210 15% 5%; /* #0B0D0F */ + --foreground: 220 14% 95%; /* #F2F3F5 */ + --border: 214 10% 17%; /* #272B30 */ + --card: 210 12% 9%; /* #15181B */ + --card-foreground: 220 14% 95%; + --card-border: 214 10% 17%; + --sidebar: 210 15% 5%; + --sidebar-foreground: 220 14% 95%; + --sidebar-border: 214 10% 17%; + --sidebar-primary: 73 100% 62%; /* #D4FF3F */ + --sidebar-primary-foreground: 210 15% 5%; + --sidebar-accent: 210 12% 11%; /* #191C20 */ + --sidebar-accent-foreground: 220 14% 95%; + --sidebar-ring: 73 100% 62%; + --popover: 210 12% 9%; + --popover-foreground: 220 14% 95%; + --popover-border: 214 10% 17%; + --primary: 73 100% 62%; /* #D4FF3F */ + --primary-foreground: 210 15% 5%; + --secondary: 210 12% 14%; + --secondary-foreground: 220 14% 95%; + --muted: 210 12% 11%; + --muted-foreground: 218 11% 65%; /* #9CA3AF */ + --accent: 210 12% 11%; + --accent-foreground: 220 14% 95%; + --destructive: 0 72% 51%; /* #E05252 */ --destructive-foreground: 0 0% 100%; - --input: 20 14% 18%; - --ring: 9 75% 61%; - --chart-1: 203.8863 88.2845% 53.1373%; - --chart-2: 159.7826 100% 36.0784%; - --chart-3: 42.0290 92.8251% 56.2745%; - --chart-4: 147.1429 78.5047% 41.9608%; - --chart-5: 341.4894 75.2000% 50.9804%; + --input: 214 10% 17%; + --ring: 73 100% 62%; + --chart-1: 73 100% 62%; + --chart-2: 159 65% 45%; + --chart-3: 38 92% 54%; + --chart-4: 18 85% 60%; + --chart-5: 0 72% 51%; --spotlight-color: rgba(255, 255, 255, 0.1); --shadow-2xs: 0px 2px 0px 0px hsl(9 75% 61% / 0.00); --shadow-xs: 0px 2px 0px 0px hsl(9 75% 61% / 0.00); diff --git a/client/src/lib/queryClient.ts b/client/src/lib/queryClient.ts index 276e123..b6ae81d 100644 --- a/client/src/lib/queryClient.ts +++ b/client/src/lib/queryClient.ts @@ -1,7 +1,7 @@ import { QueryClient, QueryFunction } from "@tanstack/react-query"; import { useErrorStore } from "./error-store"; -import { getCsrfToken } from "./csrf"; +import { getCsrfToken, clearCsrfToken } from "./csrf"; function userSafeErrorMessage(status: number, rawBody: string): string { if (status === 403) return "This action was blocked. Try refreshing the page and signing in again."; @@ -42,20 +42,36 @@ export async function apiRequest( if (data) { headers["Content-Type"] = "application/json"; } - if (UNSAFE.has(method.toUpperCase()) && url.startsWith("/api") && !url.includes("/webhooks/")) { + const isUnsafe = UNSAFE.has(method.toUpperCase()) && url.startsWith("/api") && !url.includes("/webhooks/"); + if (isUnsafe) { const csrf = await getCsrfToken(); if (csrf) { headers["X-CSRF-Token"] = csrf; } } - const res = await fetch(url, { + let res = await fetch(url, { method, headers, body: data ? JSON.stringify(data) : undefined, credentials: "include", }); + // If blocked by CSRF mismatch or stale token (403), auto-refresh CSRF token and retry once + if (res.status === 403 && isUnsafe) { + clearCsrfToken(); + const freshCsrf = await getCsrfToken(); + if (freshCsrf) { + headers["X-CSRF-Token"] = freshCsrf; + res = await fetch(url, { + method, + headers, + body: data ? JSON.stringify(data) : undefined, + credentials: "include", + }); + } + } + await throwIfResNotOk(res); return res; } diff --git a/client/src/pages/auth-page.tsx b/client/src/pages/auth-page.tsx index aa7b88d..5b8c81f 100644 --- a/client/src/pages/auth-page.tsx +++ b/client/src/pages/auth-page.tsx @@ -2,47 +2,48 @@ import { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { SiGithub } from "react-icons/si"; import { Button } from "@/components/ui/button"; +import { ArrowLeft } from "lucide-react"; import Dither from "@/components/ui/dither"; const TESTIMONIALS = [ { - text: "Your codebase's 24/7 guardian. CodeGuard autonomously scans every Pull Request for OWASP Top 10 vulnerabilities, logic flaws, and secret exposure — then fixes them before they ship.", + text: "Your codebase's 24/7 guardian. CodeSift AI autonomously scans every Pull Request for OWASP Top 10 vulnerabilities, logic flaws, and secret exposure — then fixes them before they ship.", author: "Aarav Patel, Lead Engineer" }, { - text: "Stop reviewing security bugs manually. CodeGuard acts as your Senior AppSec Engineer — detecting vulnerabilities, generating battle-tested fixes, and opening PRs automatically. Ship fast. Ship safe.", + text: "Stop reviewing security bugs manually. CodeSift AI acts as your Senior AppSec Engineer — detecting vulnerabilities, generating battle-tested fixes, and opening PRs automatically. Ship fast. Ship safe.", author: "Neha Sharma, DevOps Architect" }, { - text: "Where elite security meets premium developer experience. CodeGuard combines GPT-4o intelligence with real-time GitHub integration to turn every code review into a fortress — automatically.", + text: "Where elite security meets premium developer experience. CodeSift AI combines AI intelligence with real-time GitHub integration to turn every code review into a fortress — automatically.", author: "Vikram Singh, Security Researcher" }, { - text: "Code ships. Vulnerabilities don't. CodeGuard monitors every PR in real-time, catches what humans miss, and auto-remediates critical issues before they ever touch production.", + text: "Code ships. Vulnerabilities don't. CodeSift AI monitors every PR in real-time, catches what humans miss, and auto-remediates critical issues before they ever touch production.", author: "Ananya Gupta, VP of Engineering" }, { - text: "Built for teams who refuse to compromise. CodeGuard brings autonomous, AI-powered security analysis directly into your workflow.", + text: "Built for teams who refuse to compromise. CodeSift AI brings autonomous, AI-powered security analysis directly into your workflow.", author: "Rohan Desai, Senior Backend Developer" }, { - text: "Most vulnerabilities don't announce themselves. CodeGuard does — catching SQLi, XSS, secret leaks, and logic flaws across every PR before your users ever see them. Autonomous. Relentless. Always on.", + text: "Most vulnerabilities don't announce themselves. CodeSift AI does — catching SQLi, XSS, secret leaks, and logic flaws across every PR before your users ever see them. Autonomous. Relentless. Always on.", author: "Priya Kumar, AppSec Manager" }, { - text: "Every merged PR is a decision. CodeGuard makes sure none of them are the wrong one. Powered by GPT-4o, it reads your entire codebase context, generates architecturally sound fixes, and ships them as a PR — while you sleep.", + text: "Every merged PR is a decision. CodeSift AI makes sure none of them are the wrong one. Powered by state-of-the-art AI, it reads your codebase context, generates architecturally sound fixes, and ships them as a PR.", author: "Arjun Reddy, Staff Software Engineer" }, { - text: "Security isn't a feature. It's a standard. CodeGuard enforces that standard on every commit — scanning, scoring, and self-healing your codebase with the precision of a senior engineer and the speed of a machine.", + text: "Security isn't a feature. It's a standard. CodeSift AI enforces that standard on every commit — scanning, scoring, and self-healing your codebase with precision and speed.", author: "Neha Joshi, Director of Cloud Security" }, { - text: "Your team is shipping fast. Your security review isn't keeping up. CodeGuard closes that gap — autonomously analyzing every Pull Request, flagging what matters, and deploying fixes before vulnerabilities ever reach main.", + text: "Your team is shipping fast. Your security review isn't keeping up. CodeSift AI closes that gap — autonomously analyzing every Pull Request, flagging what matters, and deploying fixes before vulnerabilities reach main.", author: "Karan Mehta, Principal Engineer" }, { - text: "Vulnerabilities are patient. Your reviewers aren't. CodeGuard never blinks — an AI security agent that lives inside your GitHub repositories.", + text: "Vulnerabilities are patient. Your reviewers aren't. CodeSift AI never blinks — an AI security agent that lives inside your GitHub repositories.", author: "Sneha Iyer, Tech Lead" } ]; @@ -76,8 +77,8 @@ export default function AuthPage() { />
- CodeGuard Logo - CodeGuard + CodeSift AI Logo + CodeSift AI
@@ -103,10 +104,20 @@ export default function AuthPage() { {/* Right side - Login Form */}
+ {/* Back to Home Link */} + + {/* Mobile Header (Hidden on Desktop) */}
- CodeGuard Logo - CodeGuard + CodeSift AI Logo + CodeSift AI
diff --git a/client/src/pages/dashboard.tsx b/client/src/pages/dashboard.tsx index 55fa94f..b2d8722 100644 --- a/client/src/pages/dashboard.tsx +++ b/client/src/pages/dashboard.tsx @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { GitPullRequest, MessageSquare, FolderGit2, AlertTriangle } from "lucide-react"; +import { GitPullRequest, MessageSquare, FolderGit2, AlertTriangle, Shield, ShieldAlert, ShieldCheck } from "lucide-react"; import { StatsCard } from "@/components/stats-card"; import { ReviewCard } from "@/components/review-card"; import { EmptyState } from "@/components/empty-state"; @@ -109,6 +109,173 @@ export default function Dashboard() { )}
+ {/* Security Posture Card */} + {(() => { + const taintCrit = stats?.taintVulnerabilities?.critical || 0; + const taintHigh = stats?.taintVulnerabilities?.high || 0; + const taintMed = stats?.taintVulnerabilities?.medium || 0; + const taintLow = stats?.taintVulnerabilities?.low || 0; + + const prHigh = stats?.riskDistribution?.high || 0; + const prMed = stats?.riskDistribution?.medium || 0; + const prLow = stats?.riskDistribution?.low || 0; + + const criticalFindings = taintCrit; + const highFindings = taintHigh + prHigh; + const mediumFindings = taintMed + prMed; + const lowFindings = taintLow + prLow; + const totalOpenIssues = (stats?.totalComments || 0) + (stats?.totalPolicyViolations || 0) + (stats?.totalTaintPaths || 0); + + // Deterministic Score Calculation: + // Starts at 100, penalized by findings weight: Critical (-20), High (-10), Medium (-4), Low (-1), capped at 0. + // If there are no reviews or findings recorded, default clean baseline is 100. + const deductions = (criticalFindings * 20) + (highFindings * 10) + (mediumFindings * 4) + (lowFindings * 1); + const postureScore = stats?.totalReviews === 0 ? 100 : Math.max(0, Math.min(100, 100 - deductions)); + + const scoreColor = postureScore >= 80 + ? "text-emerald-500" + : postureScore >= 50 + ? "text-amber-500" + : "text-red-500"; + + const scoreBg = postureScore >= 80 + ? "bg-emerald-500" + : postureScore >= 50 + ? "bg-amber-500" + : "bg-red-500"; + + const ratingLabel = postureScore >= 80 ? "STRONG" : postureScore >= 50 ? "MODERATE" : "CRITICAL RISK"; + const ratingBadge = postureScore >= 80 + ? "border-emerald-500/30 text-emerald-600 bg-emerald-500/10 dark:text-emerald-400" + : postureScore >= 50 + ? "border-amber-500/30 text-amber-600 bg-amber-500/10 dark:text-amber-400" + : "border-red-500/30 text-red-600 bg-red-500/10 dark:text-red-400"; + + return ( + + +
+
+
+ +
+
+ + Security Posture + + {ratingLabel} + + +

+ Deterministic posture rating derived from all verified reviews, taint paths, and policy violations +

+
+
+
+
+ Posture Score + + {statsLoading ? "--" : postureScore}/100 + +
+
+
+
+ +
+ {/* Score Summary Block */} +
+ + Posture + +
+
+
+
+
+ + {postureScore >= 80 ? "Healthy" : postureScore >= 50 ? "Needs Review" : "Action Required"} + +
+ + {/* Critical Findings */} +
+
+ + Critical + + +
+

+ {statsLoading ? "--" : criticalFindings} +

+ Taint sinks & exploits +
+ + {/* High Findings */} +
+
+ + High + + +
+

+ {statsLoading ? "--" : highFindings} +

+ Severe security flaws +
+ + {/* Medium Findings */} +
+
+ + Medium + + +
+

+ {statsLoading ? "--" : mediumFindings} +

+ Moderate exposure +
+ + {/* Low Findings */} +
+
+ + Low + + +
+

+ {statsLoading ? "--" : lowFindings} +

+ Best-practice notes +
+ + {/* Open Issues */} +
+
+ + Open Issues + + +
+

+ {statsLoading ? "--" : totalOpenIssues} +

+ Total logged findings +
+
+ + + ); + })()} + {/* Operational Metrics */} diff --git a/client/src/pages/landing-page.tsx b/client/src/pages/landing-page.tsx new file mode 100644 index 0000000..2e98bd6 --- /dev/null +++ b/client/src/pages/landing-page.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { useLocation } from "wouter"; +import { motion } from "framer-motion"; +import { LandingNavbar } from "@/components/landing/navbar"; +import { HeroSection } from "@/components/landing/hero"; +import { ProblemSolutionSection } from "@/components/landing/problem-solution"; +import { FeaturesSection } from "@/components/landing/features"; +import { HowItWorksSection } from "@/components/landing/how-it-works"; +import { SecurityWorkflowSection } from "@/components/landing/pipeline-visualization"; +import { ProductShowcaseSection } from "@/components/landing/product-showcase"; +import { PricingSection } from "@/components/landing/pricing-section"; +import { FAQSection } from "@/components/landing/faq-section"; +import { FinalCTASection } from "@/components/landing/final-cta"; +import { LandingFooter } from "@/components/landing/footer"; + +export default function LandingPage() { + const [location] = useLocation(); + + return ( + + +
+ {/* 1 & 2: Hero and Product Preview */} + + + {/* 4: Problem / Solution */} + + + {/* 5: Features */} + + + {/* 6: How It Works */} + + + {/* 7: Security Workflow */} + + + {/* 8: Product Interface Showcase */} + + + {/* 9: Pricing */} + + + {/* 10: FAQ */} + + + {/* 11: Final CTA */} + +
+ {/* 12: Footer */} + +
+ ); +} diff --git a/client/src/pages/review-detail.tsx b/client/src/pages/review-detail.tsx index c013b5f..0ecf558 100644 --- a/client/src/pages/review-detail.tsx +++ b/client/src/pages/review-detail.tsx @@ -25,6 +25,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { TaintGraphViewer } from "@/components/taint-graph-viewer"; import { TaintPathsList } from "@/components/taint-paths-list"; import { PolicyViolationsList, type PolicyViolationRecord } from "@/components/PolicyViolationsList"; +import { AISecuritySummary } from "@/components/ai-security-summary"; import { isSecurityFixTitle, stripEmoji } from "@/lib/text"; import { safePrUrl } from "@/lib/safe-url"; import { formatDistanceToNow, format } from "date-fns"; @@ -210,11 +211,19 @@ export default function ReviewDetail() { - {/* Summary */} + {/* AI Security Summary Card */} + + + {/* Legacy Summary fallback if different or supplementary */} {review.summary && ( - AI Summary + Review Overview

{safeSummary}

diff --git a/client/src/pages/settings.tsx b/client/src/pages/settings.tsx index a88e100..a67c15e 100644 --- a/client/src/pages/settings.tsx +++ b/client/src/pages/settings.tsx @@ -157,7 +157,7 @@ export default function Settings() { Review Preferences
- Choose what CodeGuard should prioritize during analysis. + Choose what CodeSift AI should prioritize during analysis. {[ diff --git a/package.json b/package.json index cdbf3bb..59311b3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "license": "MIT", "scripts": { - "dev": "cross-env NODE_ENV=development tsx server/index.ts", + "dev": "cross-env NODE_ENV=development tsx watch server/index.ts", "build": "drizzle-kit push && tsx script/build.ts", "start": "cross-env NODE_ENV=production node dist/index.cjs", "check": "tsc", diff --git a/server/ai/provider.ts b/server/ai/provider.ts index d1fd370..84baae3 100644 --- a/server/ai/provider.ts +++ b/server/ai/provider.ts @@ -27,13 +27,23 @@ const openaiClient = new OpenAI({ }); /** - * Groq client for ultra-fast analysis. + * Groq client for ultra-fast analysis and primary local development. */ -const groqClient = new OpenAI({ - baseURL: "https://api.groq.com/openai/v1", - apiKey: process.env.GROQ_API_KEY ?? "", - maxRetries: 2, -}); +let cachedGroqClient: OpenAI | null = null; +let cachedGroqKey = ""; + +export function getGroqClient(key: string): OpenAI { + if (cachedGroqClient && cachedGroqKey === key) { + return cachedGroqClient; + } + cachedGroqClient = new OpenAI({ + baseURL: "https://api.groq.com/openai/v1", + apiKey: key, + maxRetries: 2, + }); + cachedGroqKey = key; + return cachedGroqClient; +} // ── Task types → Model mapping ──────────────────────────────────────────────── @@ -191,21 +201,14 @@ export async function callAI(options: AICallOptions): Promise { const maxNIMRetries = parseInt(process.env.NVIDIA_NIM_MAX_RETRIES ?? "2"); // ── Try Groq ────────────────────────────────────────────────────────────── - const groqKey = task === "analysis" + const groqKey = (task === "analysis" || task === "enrich") ? (process.env.GROQ_API_KEY_AUDIT || process.env.GROQ_API_KEY) : (process.env.GROQ_API_KEY_FIX || process.env.GROQ_API_KEY); if (groqKey) { - const groqModel = "llama-3.3-70b-versatile"; + const groqModel = process.env.GROQ_MODEL ?? "openai/gpt-oss-120b"; console.log(`[AI Provider] Routing '${task}' task to Groq (${groqModel})`); - - const currentGroqClient = groqKey === process.env.GROQ_API_KEY - ? groqClient - : new OpenAI({ - baseURL: "https://api.groq.com/openai/v1", - apiKey: groqKey, - maxRetries: 2, - }); + const currentGroqClient = getGroqClient(groqKey); try { providerStats.groqCalls++; @@ -233,9 +236,46 @@ export async function callAI(options: AICallOptions): Promise { promptTokens: response.usage?.prompt_tokens ?? 0, completionTokens: response.usage?.completion_tokens ?? 0, }; - } catch (error: any) { + } catch (err: any) { providerStats.groqFailures++; - console.warn(`[AI Provider] Groq failed: ${error.message}. Falling back to NIM/OpenAI...`); + providerStats.lastFailureReason = err?.message ?? "unknown"; + + // On model error (unsupported feature / prompt format): retry with JSON-in-prompt + if (err?.status === 400 && responseFormat?.type === "json_object") { + console.log("[AI Provider] Groq response_format rejected. Retrying with prompt-based JSON enforcement..."); + try { + const messagesCopy = [...messages]; + if (messagesCopy[0]?.role === "system") { + messagesCopy[0] = { + ...messagesCopy[0], + content: messagesCopy[0].content + "\n\nIMPORTANT: Respond ONLY with valid JSON. No prose, no markdown, no code fences." + }; + } + const retryParams: ChatCompletionCreateParamsNonStreaming = { + model: groqModel, + messages: messagesCopy, + max_tokens: maxTokens, + temperature, + }; + const retryResponse = await currentGroqClient.chat.completions.create(retryParams, { signal }); + const retryContent = retryResponse.choices[0]?.message?.content ?? ""; + + providerStats.groqSuccesses++; + providerStats.lastProvider = "groq"; + + return { + content: retryContent, + provider: "groq", + model: groqModel, + promptTokens: retryResponse.usage?.prompt_tokens ?? 0, + completionTokens: retryResponse.usage?.completion_tokens ?? 0, + }; + } catch (retryErr: any) { + console.warn(`[AI Provider] Groq JSON retry failed: ${retryErr.message}`); + } + } + + console.warn(`[AI Provider] Groq failed: ${err.message}. Falling back to NIM/OpenAI...`); } } @@ -352,12 +392,22 @@ export async function callAI(options: AICallOptions): Promise { console.warn("[AI Provider] NIM exhausted. Falling back to OpenAI."); } else if (!nimAvailable) { - console.log("[AI Provider] NVIDIA_NIM_API_KEY not set or invalid. Using OpenAI directly."); + if (!groqKey) { + console.log("[AI Provider] NVIDIA_NIM_API_KEY not set or invalid. Using OpenAI directly."); + } } else if (isCircuitOpen()) { console.log("[AI Provider] NIM circuit is OPEN. Using OpenAI directly."); } // ── Fallback: OpenAI ─────────────────────────────────────────────────────── + const openaiKey = process.env.OPENAI_API_KEY?.trim(); + if (!openaiKey) { + const lastErr = providerStats.lastFailureReason ? ` Last error: ${providerStats.lastFailureReason}` : ""; + throw new Error( + `AI request failed: No operational AI provider available. Set GROQ_API_KEY, NVIDIA_NIM_API_KEY, or OPENAI_API_KEY in .env.${lastErr}` + ); + } + try { console.log(`[AI Provider] OpenAI fallback — model: ${OPENAI_FALLBACK_MODEL}`); providerStats.openaiCalls++; @@ -392,7 +442,7 @@ export async function callAI(options: AICallOptions): Promise { providerStats.openaiFailures++; providerStats.lastFailureReason = error.message ?? "unknown"; console.error("[AI Provider] OpenAI fallback also failed:", error.message); - throw new Error(`Both AI providers failed. Last error: ${error.message}`); + throw new Error(`AI request failed. Last error: ${error.message}`); } } @@ -402,6 +452,30 @@ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } +/** + * Check if Groq is reachable with a lightweight ping. + * Used by the /api/ai/status health endpoint. + */ +export async function pingGroq(): Promise<{ reachable: boolean; latencyMs: number; error?: string }> { + const start = Date.now(); + try { + const groqKey = process.env.GROQ_API_KEY ?? process.env.GROQ_API_KEY_AUDIT ?? process.env.GROQ_API_KEY_FIX ?? ""; + if (!groqKey) { + return { reachable: false, latencyMs: 0, error: "No Groq API key configured" }; + } + const client = getGroqClient(groqKey); + await client.chat.completions.create({ + model: process.env.GROQ_MODEL ?? "openai/gpt-oss-120b", + messages: [{ role: "user", content: "Reply with: OK" }], + max_tokens: 5, + temperature: 0, + }); + return { reachable: true, latencyMs: Date.now() - start }; + } catch (err: unknown) { + return { reachable: false, latencyMs: Date.now() - start, error: (err as { message?: string }).message }; + } +} + /** * Check if NVIDIA NIM is reachable with a lightweight ping. * Used by the /api/ai/status health endpoint. diff --git a/server/auth.ts b/server/auth.ts index c4a697c..a1fe699 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -143,6 +143,7 @@ export function setupAuth(app: Express) { if (err) return next(err); req.login(user!, (loginErr) => { if (loginErr) return next(loginErr); + ensureSessionCsrfToken(req); req.session.createdAt = Date.now(); req.session.lastActivityAt = Date.now(); res.redirect("/"); @@ -160,20 +161,41 @@ export function setupAuth(app: Express) { } }); - app.post("/api/logout", (req, res, next) => { - req.logout((err) => { - if (err) { - return next(err); - } - req.session.destroy((destroyErr) => { - if (destroyErr) { - return next(destroyErr); - } - res.clearCookie("codeguard.sid", { path: "/" }); - return res.status(204).send(); + const handleLogout = (req: any, res: any) => { + const clearAuthCookies = () => { + res.clearCookie("codeguard.sid", { + path: "/", + httpOnly: true, + sameSite: process.env.NODE_ENV === "production" ? "strict" : "lax", + secure: app.get("env") === "production", }); - }); - }); + res.clearCookie("codeguard.sid", { path: "/" }); + res.clearCookie("codeguard.sid"); + }; + + try { + req.user = undefined; + if (req.session) { + req.session.destroy((err: any) => { + if (err) { + console.error("[Auth] Session destroy error:", err); + } + clearAuthCookies(); + return res.status(200).json({ success: true, message: "Logged out" }); + }); + } else { + clearAuthCookies(); + return res.status(200).json({ success: true, message: "Logged out" }); + } + } catch (err) { + console.error("[Auth] handleLogout error:", err); + clearAuthCookies(); + return res.status(200).json({ success: true, message: "Logged out" }); + } + }; + + app.post("/api/logout", handleLogout); + app.get("/api/logout", handleLogout); app.get("/api/user", (req, res) => { if (req.isAuthenticated()) { diff --git a/server/csrf.ts b/server/csrf.ts index fb28cae..a3689b6 100644 --- a/server/csrf.ts +++ b/server/csrf.ts @@ -12,6 +12,7 @@ const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]); function isCsrfExemptPath(path: string): boolean { if (path.startsWith("/api/webhooks/")) return true; if (path === "/api/csrf") return true; + if (path === "/api/logout") return true; if (path === "/api/visitors/heartbeat") return true; return false; } diff --git a/server/middleware/request-logger.ts b/server/middleware/request-logger.ts index a817ea1..e712808 100644 --- a/server/middleware/request-logger.ts +++ b/server/middleware/request-logger.ts @@ -132,7 +132,7 @@ export function requestLogger(req: Request, res: Response, next: NextFunction) { performLog(statusCode) .finally(() => { // Now actually end the response - originalEnd.call(res, chunk, encoding, callback); + originalEnd.call(res, chunk, encoding as any, callback); }); }; diff --git a/server/openai.ts b/server/openai.ts index 8f7d276..8633376 100644 --- a/server/openai.ts +++ b/server/openai.ts @@ -1,4 +1,4 @@ -import { type AIReviewResponse, aiReviewResponseSchema, apiUsageLog } from "../shared/schema.js"; +import { type AIReviewResponse, aiReviewResponseSchema, securitySummaryResponseSchema, type SecuritySummaryResponse, aiFixExplanationSchema, type AIFixExplanation, apiUsageLog } from "../shared/schema.js"; import { callAI } from "./ai/provider.js"; import { db } from "./db.js"; const BASE_SYSTEM_PROMPT = `You are a Senior App Sec Engineer. Analyze code diffs and provide a sharp, actionable JSON review. @@ -123,6 +123,9 @@ Analyze the changes and provide your review in JSON format.`; } else if (result.provider === "nim") { // rough heuristic: $0.50 per 1M prompt/completion for Llama 3 70B costUsd = ((result.promptTokens + result.completionTokens) / 1_000_000) * 0.50; + } else if (result.provider === "groq") { + // rough heuristic: $0.59 per 1M prompt, $0.79 per 1M completion for Llama 3.3 70B + costUsd = (result.promptTokens / 1_000_000) * 0.59 + (result.completionTokens / 1_000_000) * 0.79; } // Log to api_usage_log @@ -234,3 +237,190 @@ Please provide the fixed full file content.`; throw new Error(`Failed to generate fix: ${error.message}`); } } + +export interface SecuritySummaryInput { + prTitle: string; + summary?: string | null; + riskLevel?: string | null; + filesChanged?: number | null; + additions?: number | null; + deletions?: number | null; + comments: Array<{ + path: string; + line: number; + type: string; + comment: string; + severity?: string; + }>; + policyViolations?: Array<{ + ruleId: string; + ruleName: string; + severity: string; + filePath: string; + explanation?: string | null; + }>; + taintPaths?: Array<{ + title: string; + vulnerabilityType: string; + severity: string; + sourceFile: string; + sinkFile: string; + sinkExpression: string; + }>; +} + +export async function generateSecuritySummary( + input: SecuritySummaryInput, + repositoryId?: string +): Promise { + const SECURITY_SUMMARY_SYSTEM_PROMPT = `You are a Principal Application Security Architect for CodeGuard AI. +Your job is to synthesize an executive, structured AI Security Summary for a Pull/Merge Request based on the detected review findings, policy violations, and cross-file taint vulnerabilities. + +Analyze the given context and return ONLY a valid JSON object strictly matching this schema: +{ + "riskLevel": "HIGH" | "MEDIUM" | "LOW", + "securityScore": , + "keyRisks": [ + "Brief bullet point summarizing major risk 1", + "Brief bullet point summarizing major risk 2" + ], + "potentialImpact": "Concise paragraph detailing what an attacker or system failure could trigger (e.g. data breach, unauthorized privilege escalation, service disruption)", + "recommendedActions": [ + "Specific actionable engineering remediation 1", + "Specific actionable engineering remediation 2" + ], + "whyThisMatters": "Clear, direct justification explaining the business and security consequence of merging or leaving these findings unaddressed." +} + +Rules: +1. If there are CRITICAL taint paths or HIGH severity policy violations/vulnerabilities, riskLevel must be "HIGH" and securityScore must reflect severe risk (typically below 50). +2. If there are minor or zero issues, riskLevel should be "LOW" and securityScore high (80-100). +3. Do not include markdown formatting or backticks in the response. Return pure JSON.`; + + const userPrompt = ` +PR Title: ${input.prTitle} +Baseline Risk: ${input.riskLevel || "unknown"} +Diff Stats: ${input.filesChanged ?? 0} files changed (+${input.additions ?? 0} / -${input.deletions ?? 0}) +Initial Summary: ${input.summary || "No initial summary available"} + +Detected Review Findings (${input.comments.length}): +${input.comments.length === 0 ? "None" : JSON.stringify(input.comments.slice(0, 15), null, 2)} + +Policy Violations (${input.policyViolations?.length ?? 0}): +${!input.policyViolations || input.policyViolations.length === 0 ? "None" : JSON.stringify(input.policyViolations.slice(0, 10), null, 2)} + +Cross-File Taint Vulnerabilities (${input.taintPaths?.length ?? 0}): +${!input.taintPaths || input.taintPaths.length === 0 ? "None" : JSON.stringify(input.taintPaths.slice(0, 5), null, 2)} +`; + + try { + const result = await callAI({ + task: "enrich", + messages: [ + { role: "system", content: SECURITY_SUMMARY_SYSTEM_PROMPT }, + { role: "user", content: userPrompt }, + ], + responseFormat: { type: "json_object" }, + maxTokens: 1500, + }); + + console.log(`[Security Summary] Served by: ${result.provider} (${result.model})`); + + let cleaned = result.content.trim(); + if (cleaned.startsWith("```json")) { + cleaned = cleaned.replace(/^```json\s*/, "").replace(/\s*```$/, ""); + } else if (cleaned.startsWith("```")) { + cleaned = cleaned.replace(/^```\s*/, "").replace(/\s*```$/, ""); + } + + const parsed = JSON.parse(cleaned); + const validated = securitySummaryResponseSchema.safeParse(parsed); + + if (!validated.success) { + console.error("[Security Summary] Validation error:", validated.error); + // Fallback heuristic summary + return { + riskLevel: (input.riskLevel?.toUpperCase() === "HIGH" ? "HIGH" : input.riskLevel?.toUpperCase() === "MEDIUM" ? "MEDIUM" : "LOW") as "LOW" | "MEDIUM" | "HIGH", + securityScore: input.riskLevel === "high" ? 40 : input.riskLevel === "medium" ? 70 : 95, + keyRisks: input.comments.map(c => `${c.path}:${c.line} - ${c.comment}`).slice(0, 3), + potentialImpact: "Potential security vulnerabilities or policy breaches if merged without review.", + recommendedActions: ["Review flagged comments and resolve security vulnerabilities."], + whyThisMatters: "Proactively mitigating risks in pull requests prevents production incidents." + }; + } + + return validated.data; + } catch (error: any) { + console.error("[Security Summary] Generation failed:", error.message); + return { + riskLevel: (input.riskLevel?.toUpperCase() === "HIGH" ? "HIGH" : input.riskLevel?.toUpperCase() === "MEDIUM" ? "MEDIUM" : "LOW") as "LOW" | "MEDIUM" | "HIGH", + securityScore: input.riskLevel === "high" ? 35 : input.riskLevel === "medium" ? 70 : 95, + keyRisks: input.comments.length > 0 + ? input.comments.slice(0, 3).map(c => `${c.path}:${c.line} - ${c.comment}`) + : ["No high-confidence security risks identified."], + potentialImpact: input.summary || "Pending security assessment.", + recommendedActions: ["Inspect code diff and verify input validation across boundaries."], + whyThisMatters: "Maintaining high security hygiene safeguards user data and application uptime." + }; + } +} + +export async function generateFixExplanation( + fileContent: string, + issueDescription: string, + issueLine: number, + filePath: string +): Promise { + const EXPLANATION_SYSTEM_PROMPT = `You are a senior application security engineer. +Explain the security vulnerability found at the specified line and explain how an automated remediation works. + +Return ONLY a valid JSON object matching this schema: +{ + "whyThisIsARisk": "1 to 3 simple sentences explaining why this finding poses a security risk.", + "howTheFixWorks": "1 to 3 simple sentences explaining how the fix remediates the flaw (e.g. parameterization, sanitization, environment variables)." +} + +Do NOT include markdown backticks or extra commentary. Return pure JSON.`; + + const userPrompt = ` +File: ${filePath} (Line ${issueLine}) +Finding Description: ${issueDescription} + +Relevant snippet/context: +${fileContent.split("\n").slice(Math.max(0, issueLine - 10), issueLine + 10).join("\n") || fileContent.slice(0, 500)} +`; + + try { + const result = await callAI({ + task: "enrich", + messages: [ + { role: "system", content: EXPLANATION_SYSTEM_PROMPT }, + { role: "user", content: userPrompt }, + ], + responseFormat: { type: "json_object" }, + maxTokens: 500, + }); + + let cleaned = result.content.trim(); + if (cleaned.startsWith("```json")) { + cleaned = cleaned.replace(/^```json\s*/, "").replace(/\s*```$/, ""); + } else if (cleaned.startsWith("```")) { + cleaned = cleaned.replace(/^```\s*/, "").replace(/\s*```$/, ""); + } + + const parsed = JSON.parse(cleaned); + const validated = aiFixExplanationSchema.safeParse(parsed); + + if (validated.success) { + return validated.data; + } + } catch (err: any) { + console.warn(`[AI Fix Explanation] AI generation failed: ${err.message}. Using fallback.`); + } + + // Safe heuristic fallback based on issue description + return { + whyThisIsARisk: `This code pattern in ${filePath} at line ${issueLine} introduces vulnerability exposure (${issueDescription}). Untrusted input or insecure configurations can be exploited by attackers.`, + howTheFixWorks: `The fix replaces the vulnerable pattern with safe defensive coding practices, such as environment variables, input parameterization, or secure sanitizers, while preserving existing business logic.` + }; +} diff --git a/server/routes.ts b/server/routes.ts index 658b715..76c72d9 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -4,7 +4,7 @@ import { storage } from "./storage.js"; import { insertRepositorySchema, insertReviewSchema, insertReviewCommentSchema, policyViolations, reviews, repositories } from "../shared/schema.js"; import { getUncachableGitHubClient, getPullRequestDiff, getPullRequestDetails, postReviewComment, postReview, createBranch, updateFile, createPullRequest, getFileContent, setCommitGateStatus, getBranches } from "./github.js"; import { getMergeRequestDetails, getGitLabFileContent, createGitLabBranch, updateGitLabFile, createMergeRequest, postMergeRequestComment } from "./gitlab.js"; -import { analyzeCodeDiff, generateFix } from "./openai.js"; +import { analyzeCodeDiff, generateFix, generateSecuritySummary, generateFixExplanation } from "./openai.js"; import { isSensitiveFile } from "./policy/safety-guard.js"; import { runCrossFileTaintAnalysis } from "./taint/taint-orchestrator.js"; import { runPolicyEnforcement } from "./policy/policy-orchestrator.js"; @@ -503,6 +503,79 @@ export async function registerRoutes( } }); + // Generate AI Security Summary for a review + app.post("/api/reviews/:id/security-summary", async (req, res) => { + if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" }); + try { + const review = await storage.getReview(req.params.id); + if (!review) { + return res.status(404).json({ error: "Review not found" }); + } + + const repository = await storage.getRepository(review.repositoryId); + if (!repository || repository.userId !== req.user!.id) { + return res.status(403).json({ error: "Unauthorized access to review" }); + } + + const comments = await storage.getReviewComments(review.id); + + // Fetch policy violations if any exist + let violations: any[] = []; + try { + violations = await db + .select() + .from(policyViolations) + .where(eq(policyViolations.reviewId, review.id)); + } catch (err: any) { + console.warn("[API] Could not fetch policy violations for summary:", err.message); + } + + // Fetch taint paths if any exist + let taintPaths: any[] = []; + try { + taintPaths = await storage.getTaintPaths(review.id); + } catch (err: any) { + console.warn("[API] Could not fetch taint paths for summary:", err.message); + } + + const summary = await generateSecuritySummary({ + prTitle: review.prTitle, + summary: review.summary, + riskLevel: review.riskLevel, + filesChanged: review.filesChanged, + additions: review.additions, + deletions: review.deletions, + comments: comments.map((c) => ({ + path: c.path, + line: c.line, + type: c.type, + comment: c.comment, + severity: c.severity, + })), + policyViolations: violations.map((v) => ({ + ruleId: v.ruleId, + ruleName: v.ruleName, + severity: v.severity, + filePath: v.filePath, + explanation: v.explanation, + })), + taintPaths: taintPaths.map((tp) => ({ + title: tp.title, + vulnerabilityType: tp.vulnerabilityType, + severity: tp.severity, + sourceFile: tp.sourceFile, + sinkFile: tp.sinkFile, + sinkExpression: tp.sinkExpression, + })), + }, repository.id); + + res.json(summary); + } catch (error: any) { + console.error(`[API] Error generating security summary for review ${req.params.id}:`, error); + res.status(500).json({ error: error.message || "Failed to generate security summary" }); + } + }); + // ============= STATS ============= app.get("/api/stats", async (req, res) => { @@ -1341,6 +1414,58 @@ It analyzes your changes for potential bugs, security risks, performance issues, }); }); + // Get AI Fix Explanation before applying fix + app.get("/api/reviews/:reviewId/comments/:commentId/fix-explanation", async (req, res) => { + if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" }); + + try { + const { reviewId, commentId } = req.params; + + const comment = await storage.getReviewComment(commentId); + if (!comment || comment.reviewId !== reviewId) { + return res.status(404).json({ error: "Comment not found" }); + } + + const review = await storage.getReview(reviewId); + if (!review) { + return res.status(404).json({ error: "Review not found" }); + } + + const repo = await storage.getRepository(review.repositoryId); + if (!repo || repo.userId !== req.user!.id) { + return res.status(403).json({ error: "Unauthorized access to repository" }); + } + + const tokenUser = await storage.getUser(req.user!.id); + const accessToken = tokenUser?.accessToken ?? undefined; + + let fileContent = ""; + try { + if (repo.platform === "gitlab") { + const mrDetails = await getMergeRequestDetails(repo.owner, repo.name, review.prNumber); + fileContent = await getGitLabFileContent(repo.owner, repo.name, comment.path, mrDetails.sha); + } else { + const prDetails = await getPullRequestDetails(repo.owner, repo.name, review.prNumber, accessToken); + fileContent = await getFileContent(repo.owner, repo.name, comment.path, prDetails.head.sha, accessToken); + } + } catch (fileErr: any) { + console.warn(`[Fix Explanation] Could not fetch remote file content: ${fileErr.message}`); + } + + const explanation = await generateFixExplanation( + fileContent, + comment.comment, + comment.line, + comment.path + ); + + res.json(explanation); + } catch (error: any) { + console.error("[Fix Explanation] Error generating fix explanation:", error); + res.status(500).json({ error: "Failed to generate fix explanation" }); + } + }); + // Apply AI Fix app.post("/api/reviews/:reviewId/comments/:commentId/fix", async (req, res) => { if (!req.isAuthenticated()) return res.status(401).json({ error: "Not authenticated" }); diff --git a/server/routes/ai-status.ts b/server/routes/ai-status.ts index e640ae7..9f79468 100644 --- a/server/routes/ai-status.ts +++ b/server/routes/ai-status.ts @@ -1,5 +1,5 @@ import { Router, Request, Response, NextFunction } from "express"; -import { providerStats, pingNIM } from "../ai/provider.js"; +import { providerStats, pingNIM, pingGroq } from "../ai/provider.js"; const router = Router(); @@ -15,13 +15,26 @@ function requireAuth(req: Request, res: Response, next: NextFunction) { /** * GET /api/ai/status - * Returns the current state of both AI providers. + * Returns the current state of AI providers. * Used by the dashboard's Provider Health widget. */ router.get("/status", requireAuth, async (_req, res) => { - const nimPing = await pingNIM(); + const [nimPing, groqPing] = await Promise.all([pingNIM(), pingGroq()]); res.json({ + groq: { + configured: !!(process.env.GROQ_API_KEY || process.env.GROQ_API_KEY_AUDIT || process.env.GROQ_API_KEY_FIX), + reachable: groqPing.reachable, + latencyMs: groqPing.latencyMs, + pingError: groqPing.error ?? null, + model: process.env.GROQ_MODEL ?? "openai/gpt-oss-120b", + calls: providerStats.groqCalls, + successes: providerStats.groqSuccesses, + failures: providerStats.groqFailures, + successRate: providerStats.groqCalls > 0 + ? Math.round((providerStats.groqSuccesses / providerStats.groqCalls) * 100) + : 0, + }, nim: { configured: !!(process.env.NVIDIA_NIM_API_KEY?.startsWith("nvapi-")), reachable: nimPing.reachable, diff --git a/shared/schema.ts b/shared/schema.ts index 86accca..0c2dd33 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -375,6 +375,26 @@ export const aiReviewResponseSchema = z.object({ }); export type AIReviewResponse = z.infer; + +// AI Security Summary Schema +export const securitySummaryResponseSchema = z.object({ + riskLevel: z.enum(["LOW", "MEDIUM", "HIGH"]), + securityScore: z.number().min(0).max(100), + keyRisks: z.array(z.string()), + potentialImpact: z.string(), + recommendedActions: z.array(z.string()), + whyThisMatters: z.string(), +}); + +export type SecuritySummaryResponse = z.infer; + +// AI Fix Explanation Schema +export const aiFixExplanationSchema = z.object({ + whyThisIsARisk: z.string(), + howTheFixWorks: z.string(), +}); + +export type AIFixExplanation = z.infer; // Visitor Counter export const visitors = pgTable("visitors", {