diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx
index fe74f79..41f8d63 100644
--- a/client/src/pages/home.tsx
+++ b/client/src/pages/home.tsx
@@ -11,11 +11,10 @@ import {
Calendar,
RefreshCw,
Share2,
- Twitter,
- Facebook,
- Rss,
Bell,
- BellOff
+ BellOff,
+ Rss,
+ ImageOff
} from "lucide-react";
import { useNewsletters, useNewsletterSearch } from "@/lib/newsletter-data";
import { useToast } from "@/hooks/use-toast";
@@ -25,6 +24,34 @@ import { motion, AnimatePresence } from "framer-motion";
const ITEMS_PER_PAGE = 20;
+function NewsletterImage({ src, alt, onError }: { src: string | null, alt: string, onError: () => void }) {
+ const [error, setError] = useState(false);
+
+ const handleError = () => {
+ setError(true);
+ onError();
+ };
+
+ if (!src || error) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
+
export default function Home() {
const [searchQuery, setSearchQuery] = useState("");
const [isImporting, setIsImporting] = useState(false);
@@ -236,66 +263,61 @@ export default function Home() {
exit={{ opacity: 0, y: -20 }}
layout
>
-
-
-
-
- {newsletter.title}
-
-
-
-
-
-
-
- {format(new Date(newsletter.date), 'MMMM d, yyyy')}
-
-
- {(newsletter.thumbnail || newsletter.description) && (
-
- {newsletter.thumbnail && (
-
- )}
- {newsletter.description && (
-
- {newsletter.description}
-
- )}
-
- )}
-
-
+
+
+
+ {newsletter.title}
+
+
+
+
+
+
+
+ {format(new Date(newsletter.date), 'MMMM d, yyyy')}
+
+
+ {(newsletter.thumbnail || newsletter.description) && (
+
+ {newsletter.thumbnail && (
+ {
+ console.warn(`Failed to load image for newsletter: ${newsletter.id}`);
+ }}
+ />
+ )}
+ {newsletter.description && (
+
+ {newsletter.description}
+
+ )}
+
+ )}
+
))
) : (
diff --git a/server/index.ts b/server/index.ts
index 5a97cc7..71f11f1 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -6,6 +6,17 @@ const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
+// Add security headers to prevent script injection
+app.use((req, res, next) => {
+ // Prevent content injection
+ res.setHeader('Content-Security-Policy', "default-src 'self'; img-src 'self' https: data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';");
+ // Prevent browsers from MIME-sniffing
+ res.setHeader('X-Content-Type-Options', 'nosniff');
+ // XSS protection
+ res.setHeader('X-XSS-Protection', '1; mode=block');
+ next();
+});
+
app.use((req, res, next) => {
const start = Date.now();
const path = req.path;
@@ -47,19 +58,14 @@ app.use((req, res, next) => {
throw err;
});
- // importantly only setup vite in development and after
- // setting up all the other routes so the catch-all route
- // doesn't interfere with the other routes
if (app.get("env") === "development") {
await setupVite(app, server);
} else {
serveStatic(app);
}
- // ALWAYS serve the app on port 5000
- // this serves both the API and the client
const PORT = 5000;
server.listen(PORT, "0.0.0.0", () => {
log(`serving on port ${PORT}`);
});
-})();
+})();
\ No newline at end of file